This commit is contained in:
@@ -33,6 +33,8 @@ export default function AdminBoardsPage() {
|
||||
const listTypes = useMemo(() => viewTypes.filter((v: any) => v.scope === 'list'), [viewTypes]);
|
||||
const defaultMainTypeId = useMemo(() => (mainTypes.find((t: any) => t.key === 'main_default' || t.key === 'default')?.id ?? null), [mainTypes]);
|
||||
const defaultListTypeId = useMemo(() => (listTypes.find((t: any) => t.key === 'list_default' || t.key === 'default')?.id ?? null), [listTypes]);
|
||||
const textMainTypeId = useMemo(() => (mainTypes.find((t: any) => t.key === 'main_text')?.id ?? null), [mainTypes]);
|
||||
const textListTypeId = useMemo(() => (listTypes.find((t: any) => t.key === 'list_text')?.id ?? null), [listTypes]);
|
||||
const categories = useMemo(() => {
|
||||
const merged = rawCategories.map((c: any) => ({ ...c, ...(dirtyCats[c.id] ?? {}) }));
|
||||
return merged.sort((a: any, b: any) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
|
||||
@@ -237,7 +239,7 @@ export default function AdminBoardsPage() {
|
||||
return;
|
||||
}
|
||||
const sortOrder = (currentItems?.length ?? 0) + 1;
|
||||
await fetch(`/api/admin/boards`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ name, slug, categoryId: catId === "uncat" ? null : catId, sortOrder, status: "active", mainPageViewTypeId: defaultMainTypeId, listViewTypeId: defaultListTypeId }) });
|
||||
await fetch(`/api/admin/boards`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ name, slug, categoryId: catId === "uncat" ? null : catId, sortOrder, status: "active", mainPageViewTypeId: textMainTypeId, listViewTypeId: textListTypeId }) });
|
||||
await mutateBoards();
|
||||
}
|
||||
|
||||
@@ -436,12 +438,20 @@ export default function AdminBoardsPage() {
|
||||
|
||||
function BoardRowCells({ b, onDirty, onDelete, allowMove, categories, onMove, mainTypes, listTypes, onAddType, defaultMainTypeId, defaultListTypeId }: { b: any; onDirty: (id: string, draft: any) => void; onDelete: () => void; allowMove?: boolean; categories?: any[]; onMove?: (toId: string) => void; mainTypes?: any[]; listTypes?: any[]; onAddType?: (scope: 'main'|'list') => Promise<string | null>; defaultMainTypeId?: string | null; defaultListTypeId?: string | null }) {
|
||||
const [edit, setEdit] = useState(b);
|
||||
const effectiveMainTypeId = edit.mainPageViewTypeId ?? defaultMainTypeId ?? '';
|
||||
const effectiveListTypeId = edit.listViewTypeId ?? defaultListTypeId ?? '';
|
||||
// 선택 가능 옵션에서 '기본' 타입은 제외
|
||||
const selectableMainTypes = (mainTypes ?? []).filter((t: any) => t.id !== defaultMainTypeId);
|
||||
const selectableListTypes = (listTypes ?? []).filter((t: any) => t.id !== defaultListTypeId);
|
||||
// 표시 값: 현재 값이 선택 가능 목록에 없으면 첫 번째 항목을 사용
|
||||
const effectiveMainTypeId = selectableMainTypes.some((t: any) => t.id === edit.mainPageViewTypeId)
|
||||
? edit.mainPageViewTypeId
|
||||
: (selectableMainTypes[0]?.id ?? '');
|
||||
const effectiveListTypeId = selectableListTypes.some((t: any) => t.id === edit.listViewTypeId)
|
||||
? edit.listViewTypeId
|
||||
: (selectableListTypes[0]?.id ?? '');
|
||||
return (
|
||||
<>
|
||||
<td className="px-3 py-2"><input className="h-9 w-full rounded-md border border-neutral-300 px-2 text-sm" value={edit.name} onChange={(e) => { const v = { ...edit, name: e.target.value }; setEdit(v); onDirty(b.id, v); }} /></td>
|
||||
<td className="px-3 py-2"><input className="h-9 w-full rounded-md border border-neutral-300 px-2 text-sm" value={edit.slug} onChange={(e) => { const v = { ...edit, slug: e.target.value }; setEdit(v); onDirty(b.id, v); }} /></td>
|
||||
<td className="px-3 py-2"><input className="h-9 w-full min-w-[160px] rounded-md border border-neutral-300 px-2 text-sm" value={edit.name} onChange={(e) => { const v = { ...edit, name: e.target.value }; setEdit(v); onDirty(b.id, v); }} /></td>
|
||||
<td className="px-3 py-2"><input className="h-9 w-full min-w-[200px] rounded-md border border-neutral-300 px-2 text-sm" value={edit.slug} onChange={(e) => { const v = { ...edit, slug: e.target.value }; setEdit(v); onDirty(b.id, v); }} /></td>
|
||||
<td className="px-3 py-2 text-center">
|
||||
<select
|
||||
className="h-9 rounded-md border border-neutral-300 px-2 text-sm"
|
||||
@@ -453,12 +463,11 @@ function BoardRowCells({ b, onDirty, onDelete, allowMove, categories, onMove, ma
|
||||
e.currentTarget.value = id ?? '';
|
||||
return;
|
||||
}
|
||||
const v = { ...edit, mainPageViewTypeId: e.target.value || null };
|
||||
const v = { ...edit, mainPageViewTypeId: e.target.value };
|
||||
setEdit(v); onDirty(b.id, v);
|
||||
}}
|
||||
>
|
||||
<option value="">(없음)</option>
|
||||
{(mainTypes ?? []).map((t: any) => (<option key={t.id} value={t.id}>{t.name}</option>))}
|
||||
{(selectableMainTypes ?? []).map((t: any) => (<option key={t.id} value={t.id}>{t.name}</option>))}
|
||||
<option value="__add__">+ 새 타입 추가…</option>
|
||||
</select>
|
||||
</td>
|
||||
@@ -473,12 +482,11 @@ function BoardRowCells({ b, onDirty, onDelete, allowMove, categories, onMove, ma
|
||||
e.currentTarget.value = id ?? '';
|
||||
return;
|
||||
}
|
||||
const v = { ...edit, listViewTypeId: e.target.value || null };
|
||||
const v = { ...edit, listViewTypeId: e.target.value };
|
||||
setEdit(v); onDirty(b.id, v);
|
||||
}}
|
||||
>
|
||||
<option value="">(없음)</option>
|
||||
{(listTypes ?? []).map((t: any) => (<option key={t.id} value={t.id}>{t.name}</option>))}
|
||||
{(selectableListTypes ?? []).map((t: any) => (<option key={t.id} value={t.id}>{t.name}</option>))}
|
||||
<option value="__add__">+ 새 타입 추가…</option>
|
||||
</select>
|
||||
</td>
|
||||
@@ -551,8 +559,8 @@ function CategoryHeaderContent({ g, onDirty }: { g: any; onDirty: (payload: any)
|
||||
return (
|
||||
<>
|
||||
<div className="w-10" />
|
||||
<input className="h-8 rounded-md border border-neutral-300 px-2 text-sm w-48" value={edit.name} onChange={(e) => { const v = { ...edit, name: e.target.value }; setEdit(v); onDirty(v); }} />
|
||||
<input className="h-8 rounded-md border border-neutral-300 px-2 text-sm w-48" value={edit.slug} onChange={(e) => { const v = { ...edit, slug: e.target.value }; setEdit(v); onDirty(v); }} />
|
||||
<input className="h-8 rounded-md border border-neutral-300 px-2 text-sm w-48 min-w-[160px]" value={edit.name} onChange={(e) => { const v = { ...edit, name: e.target.value }; setEdit(v); onDirty(v); }} />
|
||||
<input className="h-8 rounded-md border border-neutral-300 px-2 text-sm w-48 min-w-[200px]" value={edit.slug} onChange={(e) => { const v = { ...edit, slug: e.target.value }; setEdit(v); onDirty(v); }} />
|
||||
<div className="flex-1" />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,32 @@
|
||||
import type { Metadata } from "next";
|
||||
import AdminSidebar from "@/app/admin/AdminSidebar";
|
||||
import { headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Admin | ASSM",
|
||||
};
|
||||
|
||||
export default function AdminLayout({ children }: { children: React.ReactNode }) {
|
||||
export default async function AdminLayout({ children }: { children: React.ReactNode }) {
|
||||
// 서버에서 쿠키 기반 접근 제어 (미들웨어 보조)
|
||||
const h = await headers();
|
||||
const cookieHeader = h.get("cookie") || "";
|
||||
const uid = cookieHeader
|
||||
.split(";")
|
||||
.map((s) => s.trim())
|
||||
.find((pair) => pair.startsWith("uid="))
|
||||
?.split("=")[1];
|
||||
const isAdmin = cookieHeader
|
||||
.split(";")
|
||||
.map((s) => s.trim())
|
||||
.find((pair) => pair.startsWith("isAdmin="))
|
||||
?.split("=")[1];
|
||||
if (!uid) {
|
||||
redirect("/login");
|
||||
}
|
||||
if (isAdmin !== "1") {
|
||||
redirect("/");
|
||||
}
|
||||
return (
|
||||
<div className="min-h-[calc(100vh-0px)] flex">
|
||||
<AdminSidebar />
|
||||
|
||||
Reference in New Issue
Block a user