Files
msgapp/src/app/admin/mainpage-settings/page.tsx

191 lines
7.5 KiB
TypeScript
Raw Normal View History

2025-10-31 16:27:04 +09:00
"use client";
import useSWR from "swr";
import { useState, useEffect } from "react";
import { useToast } from "@/app/components/ui/ToastProvider";
import { Modal } from "@/app/components/ui/Modal";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
export default function MainPageSettingsPage() {
const { show } = useToast();
const { data: settingsResp, mutate: mutateSettings } = useSWR<{ settings: any }>("/api/admin/mainpage-settings", fetcher);
const { data: boardsResp } = useSWR<{ boards: any[] }>("/api/admin/boards", fetcher);
const { data: catsResp } = useSWR<{ categories: any[] }>("/api/admin/categories", fetcher);
const settings = settingsResp?.settings ?? {};
const boards = boardsResp?.boards ?? [];
const categories = catsResp?.categories ?? [];
const [saving, setSaving] = useState(false);
const [showBoardModal, setShowBoardModal] = useState(false);
const [draft, setDraft] = useState({
showBanner: settings.showBanner ?? true,
showPartnerShops: settings.showPartnerShops ?? true,
visibleBoardIds: settings.visibleBoardIds ?? [],
});
// settings가 로드되면 draft 동기화
useEffect(() => {
if (settingsResp?.settings) {
setDraft({
showBanner: settings.showBanner ?? true,
showPartnerShops: settings.showPartnerShops ?? true,
visibleBoardIds: settings.visibleBoardIds ?? [],
});
}
}, [settingsResp, settings]);
async function save() {
setSaving(true);
try {
const res = await fetch("/api/admin/mainpage-settings", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(draft),
});
if (!res.ok) throw new Error("save_failed");
const data = await res.json();
// 저장된 설정으로 즉시 업데이트
await mutateSettings({ settings: data.settings }, { revalidate: false });
show("저장되었습니다.");
} catch (e) {
console.error(e);
show("저장 중 오류가 발생했습니다.");
} finally {
setSaving(false);
}
}
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-xl md:text-2xl font-bold text-neutral-900"> </h1>
<button
onClick={save}
disabled={saving}
className="h-9 px-4 rounded-md bg-neutral-900 text-white text-sm shadow disabled:opacity-60"
>
{saving ? "저장 중..." : "저장"}
</button>
</div>
<div className="space-y-4">
{/* 배너 표시 */}
<div className="rounded-xl border border-neutral-200 bg-white p-4">
<label className="flex items-center gap-2 text-sm font-medium">
<input
type="checkbox"
checked={draft.showBanner}
onChange={(e) => setDraft({ ...draft, showBanner: e.target.checked })}
/>
</label>
</div>
{/* 제휴 샾 목록 표시 */}
<div className="rounded-xl border border-neutral-200 bg-white p-4">
<label className="flex items-center gap-2 text-sm font-medium">
<input
type="checkbox"
checked={draft.showPartnerShops}
onChange={(e) => setDraft({ ...draft, showPartnerShops: e.target.checked })}
/>
</label>
</div>
{/* 보일 게시판 선택 */}
<div className="rounded-xl border border-neutral-200 bg-white p-4">
<div className="flex items-center justify-between mb-3">
<label className="block text-sm font-medium"> </label>
<button
type="button"
onClick={() => setShowBoardModal(true)}
className="h-8 px-3 rounded-md border border-neutral-300 text-sm hover:bg-neutral-50"
>
</button>
</div>
<div className="space-y-2 min-h-[60px]">
{draft.visibleBoardIds.length === 0 ? (
<p className="text-sm text-neutral-400 py-4 text-center"> .</p>
) : (
draft.visibleBoardIds.map((boardId: string) => {
const board = boards.find((b: any) => b.id === boardId);
if (!board) return null;
const category = categories.find((c: any) => c.id === board.categoryId);
return (
<div key={boardId} className="flex items-center justify-between py-2 px-3 bg-neutral-50 rounded-md">
<div className="flex items-center gap-2">
<span className="text-sm font-medium">{board.name}</span>
{category && (
<span className="text-xs text-neutral-500">({category.name})</span>
)}
</div>
<button
type="button"
onClick={() => {
setDraft({ ...draft, visibleBoardIds: draft.visibleBoardIds.filter((id: string) => id !== boardId) });
}}
className="text-xs text-red-600 hover:text-red-800"
>
</button>
</div>
);
})
)}
</div>
</div>
</div>
{/* 게시판 선택 모달 */}
<Modal open={showBoardModal} onClose={() => setShowBoardModal(false)}>
<div className="w-[600px] max-w-[90vw] max-h-[80vh] overflow-y-auto">
<div className="p-6">
<h2 className="text-lg font-bold mb-4"> </h2>
<div className="space-y-2 max-h-[400px] overflow-y-auto">
{boards
.filter((b: any) => !draft.visibleBoardIds.includes(b.id))
.map((b: any) => {
const category = categories.find((c: any) => c.id === b.categoryId);
return (
<button
key={b.id}
type="button"
onClick={() => {
setDraft({ ...draft, visibleBoardIds: [...draft.visibleBoardIds, b.id] });
setShowBoardModal(false);
}}
className="w-full text-left px-3 py-2 rounded-md border border-neutral-200 hover:bg-neutral-50 transition-colors"
>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">{b.name}</span>
{category && (
<span className="text-xs text-neutral-500">({category.name})</span>
)}
</div>
</button>
);
})}
{boards.filter((b: any) => !draft.visibleBoardIds.includes(b.id)).length === 0 && (
<p className="text-sm text-neutral-400 py-4 text-center"> .</p>
)}
</div>
<div className="mt-4 flex justify-end">
<button
type="button"
onClick={() => setShowBoardModal(false)}
className="h-9 px-4 rounded-md border border-neutral-300 text-sm hover:bg-neutral-50"
>
</button>
</div>
</div>
</div>
</Modal>
</div>
);
}