diff --git a/docker-compose.yml b/docker-compose.yml
index 886f283e..67398d52 100755
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -121,8 +121,8 @@ services:
deploy:
resources:
limits:
- cpus: "1.0"
- memory: 1G
+ cpus: "2.0"
+ memory: 2G
environment:
TZ: "Asia/Bangkok"
NODE_ENV: "development"
diff --git a/frontend/.editorconfig b/frontend/.editorconfig
new file mode 100644
index 00000000..86a63dc0
--- /dev/null
+++ b/frontend/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json
new file mode 100644
index 00000000..adb37d33
--- /dev/null
+++ b/frontend/.eslintrc.json
@@ -0,0 +1,15 @@
+// .eslintrc.json
+{
+ "root": true,
+ "extends": ["next/core-web-vitals"],
+ "parserOptions": {
+ "ecmaVersion": 2023,
+ "sourceType": "module"
+ },
+ "rules": {
+ "react/no-unescaped-entities": "off",
+ "@next/next/no-img-element": "off",
+ "no-console": ["warn", { "allow": ["warn", "error"] }]
+ },
+ "ignorePatterns": ["node_modules/", ".next/", "dist/", "coverage/"]
+}
diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json
new file mode 100644
index 00000000..6e3680be
--- /dev/null
+++ b/frontend/.prettierrc.json
@@ -0,0 +1,11 @@
+// File: .prettierrc.json
+{
+ "semi": true,
+ "singleQuote": false,
+ "tabWidth": 2,
+ "printWidth": 100,
+ "trailingComma": "es5",
+ "bracketSpacing": true,
+ "arrowParens": "always",
+ "endOfLine": "lf"
+}
diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev
index ba646530..78ac5c91 100644
--- a/frontend/Dockerfile.dev
+++ b/frontend/Dockerfile.dev
@@ -44,7 +44,7 @@ COPY --from=deps /app/node_modules /app/node_modules
# ไม่กำหนด USER ที่นี่ ปล่อยให้ compose คุม (ตอนนี้คุณใช้ user: "1000:1000")
ENV NODE_ENV=development
EXPOSE 3000
-CMD ["npm", "run", "dev"]
+CMD ["npm", "run", "dev"]yy
############ Build (production) ############
FROM deps AS builder
diff --git a/frontend/app/globals.css b/frontend/app/globals.css
index f969dbf5..5dab8877 100755
--- a/frontend/app/globals.css
+++ b/frontend/app/globals.css
@@ -2,16 +2,85 @@
@tailwind components;
@tailwind utilities;
-/* shadcn base tokens */
-:root { --radius: 0.5rem; }
-@layer base {
- html { -webkit-font-smoothing: antialiased; }
- :root { --background: 0 0% 100%; --foreground: 0 0% 3.9%; --card: 0 0% 100%; --card-foreground: 0 0% 3.9%; --popover: 0 0% 100%; --popover-foreground: 0 0% 3.9%; --primary: 0 0% 9%; --primary-foreground: 0 0% 98%; --secondary: 0 0% 96.1%; --secondary-foreground: 0 0% 9%; --muted: 0 0% 96.1%; --muted-foreground: 0 0% 45.1%; --accent: 0 0% 96.1%; --accent-foreground: 0 0% 9%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 98%; --border: 0 0% 89.8%; --input: 0 0% 89.8%; --ring: 0 0% 3.9%; --chart-1: 12 76% 61%; --chart-2: 173 58% 39%; --chart-3: 197 37% 24%; --chart-4: 43 74% 66%; --chart-5: 27 87% 67%; --radius: 0.5rem; }
- .dark { --background: 0 0% 3.9%; --foreground: 0 0% 98%; --card: 0 0% 3.9%; --card-foreground: 0 0% 98%; --popover: 0 0% 3.9%; --popover-foreground: 0 0% 98%; --primary: 0 0% 98%; --primary-foreground: 0 0% 9%; --secondary: 0 0% 14.9%; --secondary-foreground: 0 0% 98%; --muted: 0 0% 14.9%; --muted-foreground: 0 0% 63.9%; --accent: 0 0% 14.9%; --accent-foreground: 0 0% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 0 0% 98%; --border: 0 0% 14.9%; --input: 0 0% 14.9%; --ring: 0 0% 83.1%; --chart-1: 220 70% 50%; --chart-2: 160 60% 45%; --chart-3: 30 80% 55%; --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; }
+/* ====== shadcn/ui theme (light + dark) ====== */
+:root {
+ --background: 210 40% 98%;
+ --foreground: 220 15% 15%;
+
+ /* โทน “น้ำทะเล” ตามธีมของคุณ */
+ --primary: 199 90% 40%;
+ --primary-foreground: 0 0% 100%;
+
+ --secondary: 199 60% 92%;
+ --secondary-foreground: 220 15% 20%;
+
+ --muted: 210 20% 96%;
+ --muted-foreground: 220 10% 35%;
+
+ --accent: 199 95% 48%;
+ --accent-foreground: 0 0% 100%;
+
+ --destructive: 0 84% 60%;
+ --destructive-foreground: 0 0% 100%;
+
+ --card: 0 0% 100%;
+ --card-foreground: 220 15% 15%;
+
+ --popover: 0 0% 100%;
+ --popover-foreground: 220 15% 15%;
+
+ --border: 214 32% 91%;
+ --input: 214 32% 91%;
+ --ring: 199 90% 40%;
+
+ --radius: 0.8rem; /* โค้งมนตามแนวทาง UI ของโปรเจ็ค */
}
-@layer base {
- * {
- @apply border-border; }
- body {
- @apply bg-background text-foreground; } }
+.dark {
+ --background: 220 18% 10%;
+ --foreground: 0 0% 100%;
+
+ --primary: 199 95% 58%;
+ --primary-foreground: 220 18% 10%;
+
+ --secondary: 218 14% 20%;
+ --secondary-foreground: 0 0% 100%;
+
+ --muted: 220 14% 18%;
+ --muted-foreground: 220 10% 70%;
+
+ --accent: 199 95% 62%;
+ --accent-foreground: 220 18% 10%;
+
+ --destructive: 0 62% 46%;
+ --destructive-foreground: 0 0% 100%;
+
+ --card: 220 18% 12%;
+ --card-foreground: 0 0% 100%;
+
+ --popover: 220 18% 12%;
+ --popover-foreground: 0 0% 100%;
+
+ --border: 220 14% 28%;
+ --input: 220 14% 28%;
+ --ring: 199 95% 62%;
+}
+
+/* Base styling */
+@layer base {
+ * {
+ @apply border-border;
+ }
+ html,
+ body {
+ @apply h-full;
+ }
+ body {
+ @apply bg-background text-foreground antialiased;
+ }
+}
+
+/* Utility: container max width (ช่วยเรื่อง layout) */
+.container {
+ @apply mx-auto px-4;
+}
diff --git a/frontend/app/layout.jsx b/frontend/app/layout.jsx
index 2736ea00..ef736bd9 100755
--- a/frontend/app/layout.jsx
+++ b/frontend/app/layout.jsx
@@ -1,4 +1,9 @@
// app/layout.jsx
+import "./globals.css";
+import { Inter } from "next/font/google";
+
+const inter = Inter({ subsets: ["latin"] });
+
export const metadata = {
title: "DMS",
description: "Document Management System",
@@ -6,8 +11,59 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
-
- {children}
-
+
+
+
+ {/* Sidebar */}
+
+
+ {/* Main content */}
+
+ {/* Top Navbar */}
+
+
+
+
+
+
+
);
}
diff --git a/frontend/app/page.jsx b/frontend/app/page.jsx
index 33db56a4..8b97fb56 100755
--- a/frontend/app/page.jsx
+++ b/frontend/app/page.jsx
@@ -1,5 +1,64 @@
// app/page.jsx
-import { redirect } from "next/navigation";
-export default function Home() {
- redirect("/dashboard");
+"use client";
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
+
+export default function HomePage() {
+ return (
+
+
Welcome to DMS
+
+
+
+ Overview
+ Activity
+
+
+
+
+
+
+ 📑 RFAs
+
+
+ 24
+
+
+
+
+
+ 📐 Drawings
+
+
+ 112
+
+
+
+
+
+ 📤 Transmittals
+
+
+ 8
+
+
+
+
+
+
+
+
+ ✔️ User editor01 uploaded Drawing D-2025-07
+
+
✔️ Transmittal T-2025-02 issued to Contractor
+
✔️ RFA R-2025-03 marked as Resolved
+
+
+
+
+
+
+ );
}
diff --git a/frontend/next.config.js b/frontend/next.config.js
index 7f06481f..22cd7c37 100755
--- a/frontend/next.config.js
+++ b/frontend/next.config.js
@@ -1,6 +1,23 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
- experimental: { serverActions: { allowedOrigins: ["*"] } },
+ experimental: {
+ // เปิด app router (ค่าเริ่มต้นของ Next 15 อยู่แล้ว)
+ typedRoutes: false,
+ },
+ // รองรับการรันหลัง reverse proxy และกำหนด base URL ผ่าน ENV
+ env: {
+ NEXT_PUBLIC_API_BASE:
+ process.env.NEXT_PUBLIC_API_BASE || "http://localhost:8080",
+ },
+ // ปรับขนาดภาพ/โดเมนถ้าจำเป็น
+ images: {
+ remotePatterns: [
+ // { protocol: "https", hostname: "lcbp3.np-dms.work" }
+ ],
+ },
+ // เปิด SWC minify
+ swcMinify: true,
};
-module.exports = nextConfig;
\ No newline at end of file
+
+module.exports = nextConfig;
diff --git a/frontend/package.json b/frontend/package.json
index f8479129..367e5fa3 100755
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,34 +1,40 @@
{
"name": "dms-frontend",
- "version": "0.6.0",
+ "version": "1.0.0",
"private": true,
- "engines": {
- "node": ">=20.0.0"
- },
"scripts": {
"dev": "next dev -p 3000",
"build": "next build",
"start": "next start -p 3000",
"lint": "next lint",
- "shadcn": "shadcn"
+ "format": "prettier --write ."
},
"dependencies": {
- "class-variance-authority": "^0.7.1",
- "clsx": "^2.1.1",
- "framer-motion": "^11.18.2",
- "lucide-react": "^0.441.0",
- "next": "^15.5.3",
+ "next": "15.0.3",
"react": "18.3.1",
"react-dom": "18.3.1",
- "tailwind-merge": "^2.6.0",
- "tailwindcss-animate": "^1.0.7"
+ "tailwindcss": "3.4.14",
+ "tailwindcss-animate": "1.0.7",
+ "postcss": "8.4.47",
+ "autoprefixer": "10.4.20",
+ "class-variance-authority": "^0.7.0",
+ "clsx": "^2.1.1",
+ "tailwind-merge": "^2.2.1",
+ "framer-motion": "^11.2.10",
+ "lucide-react": "^0.451.0",
+ "@radix-ui/react-dropdown-menu": "^2.1.6",
+ "@radix-ui/react-tooltip": "^1.1.7",
+ "@radix-ui/react-tabs": "^1.1.3",
+ "@radix-ui/react-switch": "^1.1.2",
+ "@radix-ui/react-label": "^2.1.2",
+ "@radix-ui/react-slot": "^1.1.0"
},
"devDependencies": {
- "autoprefixer": "^10.4.21",
- "eslint": "^9.9.0",
- "eslint-config-next": "15.0.1",
- "postcss": "^8.5.6",
- "shadcn": "^3.3.1",
- "tailwindcss": "^3.4.17"
+ "eslint": "9.13.0",
+ "eslint-config-next": "15.0.3",
+ "prettier": "3.3.3"
+ },
+ "engines": {
+ "node": ">=20.0.0"
}
}
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
index ee1e9407..9fabed7d 100755
--- a/frontend/tailwind.config.js
+++ b/frontend/tailwind.config.js
@@ -1,115 +1,74 @@
-/** @type {import("tailwindcss").Config} */
+/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
- "./app/**/*.{js,jsx}",
- "./pages/**/*.{js,jsx}",
- "./components/**/*.{js,jsx}",
- "./src/**/*.{js,jsx}"
+ "./app/**/*.{js,jsx,ts,tsx,mdx}",
+ "./components/**/*.{js,jsx,ts,tsx,mdx}",
+ "./pages/**/*.{js,jsx,ts,tsx,mdx}", // เผื่อมีโฟลเดอร์ pages
+ "./src/**/*.{js,jsx,ts,tsx,mdx}", // เผื่อคุณเก็บ component ใน src
],
theme: {
- extend: {
- borderRadius: {
- lg: 'var(--radius)',
- md: 'calc(var(--radius) - 2px)',
- sm: 'calc(var(--radius) - 4px)'
- },
- colors: {
- background: 'hsl(var(--background))',
- foreground: 'hsl(var(--foreground))',
- card: {
- DEFAULT: 'hsl(var(--card))',
- foreground: 'hsl(var(--card-foreground))'
- },
- popover: {
- DEFAULT: 'hsl(var(--popover))',
- foreground: 'hsl(var(--popover-foreground))'
- },
- primary: {
- DEFAULT: 'hsl(var(--primary))',
- foreground: 'hsl(var(--primary-foreground))'
- },
- secondary: {
- DEFAULT: 'hsl(var(--secondary))',
- foreground: 'hsl(var(--secondary-foreground))'
- },
- muted: {
- DEFAULT: 'hsl(var(--muted))',
- foreground: 'hsl(var(--muted-foreground))'
- },
- accent: {
- DEFAULT: 'hsl(var(--accent))',
- foreground: 'hsl(var(--accent-foreground))'
- },
- destructive: {
- DEFAULT: 'hsl(var(--destructive))',
- foreground: 'hsl(var(--destructive-foreground))'
- },
- border: 'hsl(var(--border))',
- input: 'hsl(var(--input))',
- ring: 'hsl(var(--ring))',
- chart: {
- '1': 'hsl(var(--chart-1))',
- '2': 'hsl(var(--chart-2))',
- '3': 'hsl(var(--chart-3))',
- '4': 'hsl(var(--chart-4))',
- '5': 'hsl(var(--chart-5))'
- }
- }
- }
+ container: {
+ center: true,
+ padding: "2rem",
+ screens: { "2xl": "1400px" },
+ },
+ extend: {
+ colors: {
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
+ primary: {
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--destructive))",
+ foreground: "hsl(var(--destructive-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
+ },
+ popover: {
+ DEFAULT: "hsl(var(--popover))",
+ foreground: "hsl(var(--popover-foreground))",
+ },
+ card: {
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
+ },
+ },
+ borderRadius: {
+ lg: "var(--radius)",
+ md: "calc(var(--radius) - 2px)",
+ sm: "calc(var(--radius) - 4px)",
+ },
+ keyframes: {
+ "accordion-down": {
+ from: { height: "0" },
+ to: { height: "var(--radix-accordion-content-height)" },
+ },
+ "accordion-up": {
+ from: { height: "var(--radix-accordion-content-height)" },
+ to: { height: "0" },
+ },
+ },
+ animation: {
+ "accordion-down": "accordion-down 0.2s ease-out",
+ "accordion-up": "accordion-up 0.2s ease-out",
+ },
+ },
},
- plugins: [require("tailwindcss-animate")]
+ plugins: [require("tailwindcss-animate")],
};
-"EOF"
-
-cat > postcss.config.js < app/globals.css < jsconfig.json < components.json < lib/utils.js <