feat: แกไขสวน pages.jsx, layout.jsx
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
// frontend/app/(auth)/layout.jsx
|
||||
|
||||
export const metadata = {
|
||||
title: "Authentication | DMS",
|
||||
description:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,49 +1,180 @@
|
||||
// frontend/app/(protected)/layout.jsx
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getSession } from "@/lib/auth";
|
||||
import { can } from "@/lib/rbac";
|
||||
|
||||
export const metadata = { title: "DMS | Protected" };
|
||||
|
||||
export default async function ProtectedLayout({ children }) {
|
||||
// ตรวจ session ฝั่งเซิร์ฟเวอร์ ด้วยคุกกี้จริง
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return (
|
||||
<html><body><script>location.replace('/login');</script></body></html>
|
||||
);
|
||||
redirect("/login");
|
||||
}
|
||||
const { user } = session;
|
||||
|
||||
return (
|
||||
<section className="mx-auto max-w-7xl p-4 grid grid-cols-12 gap-6">
|
||||
<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="rounded-3xl p-4 border bg-white/70">
|
||||
<div className="text-sm mb-3">RBAC: <b>{user.role}</b></div>
|
||||
<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 rounded-xl px-4 py-2 bg-white/60 hover:bg-white" href="/dashboard">แดชบอร์ด</Link>
|
||||
<Link className="block rounded-xl px-4 py-2 bg-white/60 hover:bg-white" href="/drawings">Drawings</Link>
|
||||
<Link className="block rounded-xl px-4 py-2 bg-white/60 hover:bg-white" href="/rfas">RFAs</Link>
|
||||
<Link className="block rounded-xl px-4 py-2 bg-white/60 hover:bg-white" href="/transmittals">Transmittals</Link>
|
||||
<Link className="block rounded-xl px-4 py-2 bg-white/60 hover:bg-white" href="/correspondences">Correspondences</Link>
|
||||
<Link className="block rounded-xl px-4 py-2 bg-white/60 hover:bg-white" href="/contracts-volumes">Contracts & Volumes</Link>
|
||||
<Link className="block rounded-xl px-4 py-2 bg-white/60 hover:bg-white" href="/reports">Reports</Link>
|
||||
{can(user, 'workflow:view') && <Link className="block rounded-xl px-4 py-2 bg-white/60 hover:bg-white" href="/workflow">Workflow (n8n)</Link>}
|
||||
{can(user, 'health:view') && <Link className="block rounded-xl px-4 py-2 bg-white/60 hover:bg-white" href="/health">Health</Link>}
|
||||
{can(user, 'users:manage') && <Link className="block rounded-xl px-4 py-2 bg-white/60 hover:bg-white" href="/users">ผู้ใช้/บทบาท</Link>}
|
||||
<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>
|
||||
</aside>
|
||||
|
||||
<main className="col-span-12 lg:col-span-9 xl:col-span-9 space-y-6">
|
||||
{/* ปุ่ม System + Quick Actions เบื้องต้น */}
|
||||
<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="text-lg font-semibold flex-1">Document Management System — LCP3 Phase 3</div>
|
||||
{can(user, 'admin:view') && <a className="px-3 py-2 rounded-xl text-white" style={{background:'#0D5C75'}} href="/admin">Admin</a>}
|
||||
{can(user, 'users:manage') && <a className="px-3 py-2 rounded-xl text-white" style={{background:'#0D5C75'}} href="/users">ผู้ใช้/บทบาท</a>}
|
||||
{can(user, 'health:view') && <a className="px-3 py-2 rounded-xl text-white" style={{background:'#0D5C75'}} href="/health">Health</a>}
|
||||
{can(user, 'workflow:view') && <a className="px-3 py-2 rounded-xl text-white" style={{background:'#0D5C75'}} href="/workflow">Workflow</a>}
|
||||
{can(user, 'rfa:create') && <a className="px-3 py-2 rounded-xl text-white" style={{background:'#0D5C75'}} href="/rfas/new">+ RFA</a>}
|
||||
{can(user, 'drawing:upload') && <a className="px-3 py-2 rounded-xl text-white" style={{background:'#0D5C75'}} href="/drawings/upload">+ Upload Drawing</a>}
|
||||
{can(user, 'transmittal:create') && <a className="px-3 py-2 rounded-xl text-white" style={{background:'#0D5C75'}} href="/transmittals/new">+ Transmittal</a>}
|
||||
{can(user, 'correspondence:create') && <a className="px-3 py-2 rounded-xl text-white" style={{background:'#0D5C75'}} href="/correspondences/new">+ หนังสือสื่อสาร</a>}
|
||||
<div className="flex-1 text-lg font-semibold">
|
||||
Document Management System — LCP3 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>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
export const metadata = { title: "DMS – Laem Chabang Phase 3", description: "Document Management System" };
|
||||
// app/layout.jsx
|
||||
export const metadata = {
|
||||
title: "DMS",
|
||||
description: "Document Management System",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="th">
|
||||
<html lang="en">
|
||||
<body className="min-h-screen bg-[linear-gradient(180deg,#F3FBFD_0%,#E6F7FB_100%)]">
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
// app/page.jsx
|
||||
import { redirect } from "next/navigation";
|
||||
export default function Index() { redirect("/dashboard"); }
|
||||
export default function Home() {
|
||||
redirect("/dashboard");
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": false,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"registries": {}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// lib/auth.js
|
||||
import { cookies } from "next/headers";
|
||||
import { API_BASE } from "./api";
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// lib/rbac.js
|
||||
export function can(user, perm) {
|
||||
const set = new Set(user?.permissions || []);
|
||||
return set.has(perm);
|
||||
}
|
||||
export function inRole(user, ...roles) {
|
||||
return roles.includes(user?.role);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,50 @@
|
||||
// frontend/middleware.ts
|
||||
import { NextResponse } from "next/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
// ชื่อคุกกี้ให้ตรงกับ backend
|
||||
// ให้ตรงกับชื่อคุกกี้ที่ backend เซ็ต
|
||||
const COOKIE_NAME = "access_token";
|
||||
// หน้าที่เปิดได้โดยไม่ต้องล็อกอิน (ถ้าต้องการเพิ่มให้ใส่เพิ่มที่นี่)
|
||||
const PUBLIC_PREFIXES = ["/login", "/register", "/health", "/api/health"];
|
||||
// const PUBLIC_PATHS = new Set<string>(["/login", "/register", "/health"]);
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
const { pathname } = req.nextUrl;
|
||||
const hasToken = req.cookies.get("access_token");
|
||||
// ตรวจคุกกี้
|
||||
const hastoken = req.cookies.get(COOKIE_NAME)?.value;
|
||||
if (!hasToken) {
|
||||
const loginUrl = new URL("/login", req.url);
|
||||
// จำเส้นทางเดิมไว้เพื่อเด้งกลับหลังล็อกอิน
|
||||
loginUrl.searchParams.set("from", pathname);
|
||||
return NextResponse.redirect(loginUrl);
|
||||
const { pathname, search } = req.nextUrl;
|
||||
|
||||
// อนุญาตไฟล์สาธารณะและ Static assets
|
||||
if (
|
||||
pathname.startsWith("/_next/") ||
|
||||
pathname.startsWith("/public/") ||
|
||||
pathname === "/favicon.ico"
|
||||
) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// หน้าสาธารณะ: ปล่อยผ่าน
|
||||
// if (PUBLIC_PATHS.has(pathname)) {
|
||||
// return NextResponse.next();
|
||||
// }
|
||||
// อนุญาตหน้า public
|
||||
if (PUBLIC_PREFIXES.some((p) => pathname.startsWith(p))) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// ตรวจ token จาก cookie
|
||||
const token = req.cookies.get(COOKIE_NAME)?.value;
|
||||
|
||||
// ไม่มี token → เด้งไป /login พร้อม next=path เดิม
|
||||
if (!token) {
|
||||
const url = req.nextUrl.clone();
|
||||
url.pathname = "/login";
|
||||
url.searchParams.set("next", pathname + (search || ""));
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
|
||||
// มี token → ผ่าน
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// จำกัดให้ตรวจเฉพาะเส้นทาง protected (ลดโอเวอร์เฮด)
|
||||
export const config = {
|
||||
matcher: [
|
||||
"/dashboard/:path*",
|
||||
|
||||
Reference in New Issue
Block a user