Update frontend new build dev, proc
This commit is contained in:
@@ -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
9
frontend/.editorconfig
Normal 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
15
frontend/.eslintrc.json
Normal 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
11
frontend/.prettierrc.json
Normal file
@@ -0,0 +1,11 @@
|
||||
// File: .prettierrc.json
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user