251002 frontend/app/(protected)/layout.jsx
This commit is contained in:
@@ -1,31 +1,33 @@
|
||||
// File: frontend/app/layout.jsx
|
||||
// frontend/app/layout.jsx
|
||||
import "./globals.css";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { cookies, headers } from "next/headers";
|
||||
// ถ้ามี lib rbac เดิมà¸à¸¢à¸¹à¹ˆà¹ƒà¸«à¹‰à¹ƒà¸Šà¹‰à¸•่à¸à¹„ด้
|
||||
import { can } from "@/lib/rbac";
|
||||
|
||||
// à¹à¸à¹‰ title ให้ถูà¸à¸ªà¸°à¸à¸”
|
||||
export const metadata = { title: "DMS | Protected" };
|
||||
export const metadata = {
|
||||
title: "DMS",
|
||||
description: "Document Management System — LCBP3 Phase 3",
|
||||
};
|
||||
|
||||
const API_BASE = (process.env.NEXT_PUBLIC_API_BASE || "").replace(/\/$/, "");
|
||||
|
||||
async function fetchSessionFromAPI() {
|
||||
// ดึงคุà¸à¸à¸µà¹‰à¸ˆà¸£à¸´à¸‡à¸ˆà¸²à¸à¸à¸±à¹ˆà¸‡à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œ à¹à¸¥à¹‰à¸§à¸ªà¹ˆà¸‡à¸•่à¸à¹ƒà¸«à¹‰ backend
|
||||
const cookieHeader = cookies().toString(); // serialize ทั้งชุด
|
||||
const hostHdr = headers().get("host");
|
||||
const protoHdr = headers().get("x-forwarded-proto") || "https";
|
||||
/** ดึงสถานะผู้ใช้แบบ global (ไม่บังคับล็อกอิน) */
|
||||
async function fetchGlobalSession() {
|
||||
const cookieStore = await cookies();
|
||||
const cookieHeader = cookieStore.toString();
|
||||
|
||||
const hdrs = await headers();
|
||||
const hostHdr = hdrs.get("host");
|
||||
const protoHdr = hdrs.get("x-forwarded-proto") || "https";
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/auth/me`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Cookie: cookieHeader,
|
||||
// เผื่ภbackend ตรวจ origin/proto/host
|
||||
"X-Forwarded-Host": hostHdr || "",
|
||||
"X-Forwarded-Proto": protoHdr,
|
||||
Accept: "application/json",
|
||||
},
|
||||
// server component ไม่ต้à¸à¸‡à¹ƒà¸Šà¹‰ credentials
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
@@ -38,75 +40,75 @@ async function fetchSessionFromAPI() {
|
||||
}
|
||||
}
|
||||
|
||||
export default async function ProtectedLayout({ children }) {
|
||||
const session = await fetchSessionFromAPI();
|
||||
if (!session) {
|
||||
// พยายามส่ง next path à¸à¸¥à¸±à¸šà¹„ปที่ /login
|
||||
redirect("/login?next=/dashboard");
|
||||
}
|
||||
const { user } = session;
|
||||
/** ปุ่ม Logout แบบ Server Action (ไม่ต้องมี client component) */
|
||||
async function LogoutAction() {
|
||||
"use server";
|
||||
const cookieStore = await cookies();
|
||||
const cookieHeader = cookieStore.toString();
|
||||
|
||||
const hdrs = await headers();
|
||||
const hostHdr = hdrs.get("host");
|
||||
const protoHdr = hdrs.get("x-forwarded-proto") || "https";
|
||||
|
||||
// เรียก backend ให้ลบคุกกี้ออก (HttpOnly cookies)
|
||||
await fetch(`${API_BASE}/api/auth/logout`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Cookie: cookieHeader,
|
||||
"X-Forwarded-Host": hostHdr || "",
|
||||
"X-Forwarded-Proto": protoHdr,
|
||||
Accept: "application/json",
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
// กลับไปหน้า login พร้อม next ไป dashboard
|
||||
redirect("/login?next=/dashboard");
|
||||
}
|
||||
|
||||
export default async function RootLayout({ children }) {
|
||||
const session = await fetchGlobalSession();
|
||||
const loggedIn = !!session?.user;
|
||||
|
||||
return (
|
||||
<section className="grid grid-cols-12 gap-6 p-4 mx-auto max-w-7xl">
|
||||
<aside className="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||
<div className="p-4 border rounded-3xl bg-white/70">
|
||||
<div className="mb-3 text-sm">RBAC: <b>{user.role}</b></div>
|
||||
<html lang="th">
|
||||
<body className="bg-slate-50">
|
||||
{/* Header รวมทุกหน้า */}
|
||||
<header className="flex items-center justify-between w-full px-4 py-3 text-white bg-sky-900">
|
||||
<h1 className="font-bold">Document Management System</h1>
|
||||
|
||||
<nav className="space-y-2">
|
||||
<Link className="block px-4 py-2 rounded-xl bg-white/60 hover:bg-white" href="/dashboard">à¹à¸”ชบà¸à¸£à¹Œà¸”</Link>
|
||||
<Link className="block px-4 py-2 rounded-xl bg-white/60 hover:bg-white" href="/drawings">Drawings</Link>
|
||||
<Link className="block px-4 py-2 rounded-xl bg-white/60 hover:bg-white" href="/rfas">RFAs</Link>
|
||||
<Link className="block px-4 py-2 rounded-xl bg-white/60 hover:bg-white" href="/transmittals">Transmittals</Link>
|
||||
<Link className="block px-4 py-2 rounded-xl bg-white/60 hover:bg-white" href="/correspondences">Correspondences</Link>
|
||||
<Link className="block px-4 py-2 rounded-xl bg-white/60 hover:bg-white" href="/contracts-volumes">Contracts & Volumes</Link>
|
||||
<Link className="block px-4 py-2 rounded-xl bg-white/60 hover:bg-white" href="/reports">Reports</Link>
|
||||
|
||||
{can(user, "workflow:view") && (
|
||||
<Link className="block px-4 py-2 rounded-xl bg-white/60 hover:bg-white" href="/workflow">Workflow (n8n)</Link>
|
||||
<div className="flex items-center gap-3">
|
||||
{loggedIn ? (
|
||||
<div className="text-sm">
|
||||
สวัสดี, <b>{session.user.username}</b> ({session.user.role})
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm">ยังไม่ได้เข้าสู่ระบบ</div>
|
||||
)}
|
||||
{can(user, "health:view") && (
|
||||
<Link className="block px-4 py-2 rounded-xl bg-white/60 hover:bg-white" href="/health">Health</Link>
|
||||
|
||||
{/* ปุ่ม Login/Logout */}
|
||||
{loggedIn ? (
|
||||
<form action={LogoutAction}>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-3 py-1.5 rounded-lg bg-white/10 hover:bg-white/20"
|
||||
>
|
||||
ออกจากระบบ
|
||||
</button>
|
||||
</form>
|
||||
) : (
|
||||
<Link
|
||||
href="/login?next=/dashboard"
|
||||
className="px-3 py-1.5 rounded-lg bg-white/10 hover:bg-white/20"
|
||||
>
|
||||
เข้าสู่ระบบ
|
||||
</Link>
|
||||
)}
|
||||
{can(user, "users:manage") && (
|
||||
<Link className="block px-4 py-2 rounded-xl bg-white/60 hover:bg-white" href="/users">ผู้ใช้/บทบาท</Link>
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="col-span-12 space-y-6 lg:col-span-9 xl:col-span-9">
|
||||
{/* System / Quick Actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 text-lg font-semibold">Document Management System — LCBP3 Phase 3</div>
|
||||
|
||||
{can(user, "admin:view") && (
|
||||
<a className="px-3 py-2 text-white rounded-xl" style={{ background: "#0D5C75" }} href="/admin">Admin</a>
|
||||
)}
|
||||
{can(user, "users:manage") && (
|
||||
<a className="px-3 py-2 text-white rounded-xl" style={{ background: "#0D5C75" }} href="/users">ผู้ใช้/บทบาท</a>
|
||||
)}
|
||||
{can(user, "health:view") && (
|
||||
<a className="px-3 py-2 text-white rounded-xl" style={{ background: "#0D5C75" }} href="/health">Health</a>
|
||||
)}
|
||||
{can(user, "workflow:view") && (
|
||||
<a className="px-3 py-2 text-white rounded-xl" style={{ background: "#0D5C75" }} href="/workflow">Workflow</a>
|
||||
)}
|
||||
{can(user, "rfa:create") && (
|
||||
<a className="px-3 py-2 text-white rounded-xl" style={{ background: "#0D5C75" }} href="/rfas/new">+ RFA</a>
|
||||
)}
|
||||
{can(user, "drawing:upload") && (
|
||||
<a className="px-3 py-2 text-white rounded-xl" style={{ background: "#0D5C75" }} href="/drawings/upload">+ Upload Drawing</a>
|
||||
)}
|
||||
{can(user, "transmittal:create") && (
|
||||
<a className="px-3 py-2 text-white rounded-xl" style={{ background: "#0D5C75" }} href="/transmittals/new">+ Transmittal</a>
|
||||
)}
|
||||
{can(user, "correspondence:create") && (
|
||||
<a className="px-3 py-2 text-white rounded-xl" style={{ background: "#0D5C75" }} href="/correspondences/new">+ หนังสืà¸à¸ªà¸·à¹ˆà¸à¸ªà¸²à¸£</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{children}
|
||||
</main>
|
||||
</section>
|
||||
<main>{children}</main>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user