Update frontend new build dev, proc

This commit is contained in:
admin
2025-09-29 16:30:21 +07:00
parent 7dd5ce8015
commit a337732d47
11 changed files with 349 additions and 148 deletions

View File

@@ -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"

9
frontend/.editorconfig Normal file
View File

@@ -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

15
frontend/.eslintrc.json Normal file
View File

@@ -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/"]
}

11
frontend/.prettierrc.json Normal file
View File

@@ -0,0 +1,11 @@
// File: .prettierrc.json
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"printWidth": 100,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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 (
<body className="min-h-screen bg-[linear-gradient(180deg,#F3FBFD_0%,#E6F7FB_100%)]">
{children}
</body>
<html lang="en" suppressHydrationWarning>
<body
className={`${inter.className} min-h-screen bg-background text-foreground`}
>
<div className="flex min-h-screen">
{/* Sidebar */}
<aside className="w-64 bg-primary text-primary-foreground p-4">
<h1 className="text-xl font-bold mb-6">📂 DMS</h1>
<nav className="space-y-2">
<a
href="/dashboard"
className="block px-3 py-2 rounded hover:bg-accent"
>
Dashboard
</a>
<a
href="/correspondences"
className="block px-3 py-2 rounded hover:bg-accent"
>
Correspondences
</a>
<a
href="/users"
className="block px-3 py-2 rounded hover:bg-accent"
>
Users
</a>
<a
href="/health"
className="block px-3 py-2 rounded hover:bg-accent"
>
Health
</a>
</nav>
</aside>
{/* Main content */}
<main className="flex-1 flex flex-col">
{/* Top Navbar */}
<header className="h-14 bg-secondary text-secondary-foreground flex items-center justify-between px-6 shadow-sm">
<span className="font-medium">Laem Chabang Port Phase 3</span>
<div className="flex items-center space-x-4">
<button className="px-3 py-1 rounded bg-accent text-accent-foreground hover:opacity-90">
Quick Action
</button>
<span className="text-sm">superadmin</span>
</div>
</header>
<section className="p-6 flex-1">{children}</section>
</main>
</div>
</body>
</html>
);
}

View File

@@ -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 (
<div className="space-y-6">
<h2 className="text-2xl font-bold">Welcome to DMS</h2>
<Tabs defaultValue="overview" className="w-full">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="activity">Activity</TabsTrigger>
</TabsList>
<TabsContent value="overview">
<div className="grid md:grid-cols-3 gap-4 mt-4">
<Card>
<CardHeader>
<CardTitle>📑 RFAs</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">24</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>📐 Drawings</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">112</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>📤 Transmittals</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">8</p>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="activity">
<div className="mt-4 space-y-3">
<p>
User <b>editor01</b> uploaded Drawing D-2025-07
</p>
<p> Transmittal T-2025-02 issued to Contractor</p>
<p> RFA R-2025-03 marked as Resolved</p>
</div>
</TabsContent>
</Tabs>
<Button className="mt-6">Go to Dashboard</Button>
</div>
);
}

View File

@@ -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;
module.exports = nextConfig;

View File

@@ -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"
}
}

View File

@@ -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 <<EOF
module.exports = {
plugins: { tailwindcss: {}, autoprefixer: {} }
};
"EOF"
cat > app/globals.css <<EOF
@tailwind base;
@tailwind components;
@tailwind utilities;
:root { --radius: 0.5rem; }
@layer base { html { -webkit-font-smoothing: antialiased; } }
"EOF"
cat > jsconfig.json <<EOF
{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["*"] } } }
"EOF"
cat > components.json <<EOF
{
"": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": false,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
"EOF"
cat > lib/utils.js <<EOF
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs){ return twMerge(clsx(inputs)); }
"EOF"
npx -p shadcn@latest shadcn init -t next -y
npx -p shadcn@latest shadcn add -y button badge card input tabs progress dropdown-menu tooltip switch
chown -R 1000:1000 /app
echo "✅ shadcn + tailwind generated under /app"
ls -la components/ui | head