fronted แก้ layout build dev&proc

This commit is contained in:
admin
2025-10-05 10:57:54 +07:00
parent 02e509986b
commit 5dec188744
5 changed files with 270 additions and 97 deletions

View File

@@ -1,85 +1,91 @@
// frontend/app/(protected)/layout.jsx
import Link from "next/link";
// frontend/app/(protected)/layout.jsx
import { cookies, headers } from "next/headers"; // 1. ยังคงใช้ฟังก์ชันฝั่ง Server
import { redirect } from "next/navigation";
import { usePathname } from 'next/navigation';
import { cookies, headers } from "next/headers";
import { can } from "@/lib/rbac";
import { Home, FileText, Users, Settings } from 'lucide-react'; // เพิ่ม Users, Settings หรือไอคอนที่ต้องการ
import { Bell, Users } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
// 2. Import Navigation Component ที่เราสร้างขึ้นมาใหม่
import { Navigation } from "./_components/navigation";
export const metadata = { title: "DMS | Protected" };
const API_BASE = (process.env.NEXT_PUBLIC_API_BASE || "").replace(/\/$/, "");
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001";
async function fetchSessionFromAPI() {
const cookieStore = await cookies(); // ✅ ต้อง await
const cookieHeader = cookieStore.toString();
async function fetchSession() {
const cookieStore = cookies();
const token = cookieStore.get("access_token")?.value;
const hdrs = await headers(); // ✅ ต้อง await
const hostHdr = hdrs.get("host");
const protoHdr = hdrs.get("x-forwarded-proto") || "https";
if (!token) return null;
const res = await fetch(`${API_BASE}/api/auth/me`, {
method: "GET",
headers: {
Cookie: cookieHeader,
"X-Forwarded-Host": hostHdr || "",
"X-Forwarded-Proto": protoHdr,
Accept: "application/json",
},
cache: "no-store",
});
if (!res.ok) return null;
try {
const data = await res.json();
return data?.ok ? data : null;
} catch {
const res = await fetch(`${API_BASE}/api/auth/me`, {
headers: { Authorization: `Bearer ${token}` },
cache: "no-store",
});
if (!res.ok) return null;
return await res.json();
} catch (error) {
console.error("Failed to fetch session:", error);
return null;
}
}
export default async function ProtectedLayout({ children }) {
const session = await fetchSessionFromAPI();
if (!session) {
redirect("/login?next=/dashboard");
// 3. ดึงข้อมูล Session บน Server
const session = await fetchSession();
// ถ้าไม่มี session หรือ user ให้ redirect ไปหน้า login
if (!session?.user) {
redirect("/login");
}
const { user } = session;
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>
<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>}
{can(user, "health:view") && <Link className="block px-4 py-2 rounded-xl bg-white/60 hover:bg-white" href="/health">Health</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>
<div className="grid min-h-screen w-full md:grid-cols-[220px_1fr] lg:grid-cols-[280px_1fr]">
<aside className="hidden border-r bg-muted/40 md:block">
{/* 4. ใช้ Navigation Component และส่งข้อมูล user เป็น props */}
<Navigation user={session.user} />
</aside>
<main className="col-span-12 space-y-6 lg:col-span-9 xl:col-span-9">
<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>
<div className="flex flex-col">
<header className="flex h-14 items-center gap-4 border-b bg-muted/40 px-4 lg:h-[60px] lg:px-6">
{/* Mobile navigation can be here */}
<div className="flex-1 w-full">
{/* Optional: Add a search bar */}
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" size="icon" className="rounded-full">
<Users className="w-5 h-5" />
<span className="sr-only">Toggle user menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>{session.user.username || 'My Account'}</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuSeparator />
{/* Logout button in client-side auth context handles the action */}
<DropdownMenuItem>Logout</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</header>
<main className="flex-1 p-4 lg:p-6">
{children}
</main>
</div>
</div>
);
}
}