Files
lcbp3.np-dms.work/frontend/app/(protected)/dashboard/page copy.jsx

978 lines
37 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// frontend/app//(protected)/dashboard/page.jsx
"use client";
import React from "react";
import Link from "next/link";
import { motion } from "framer-motion";
import {
LayoutDashboard,
FileText,
Files,
Send,
Layers,
Users,
Settings,
Activity,
Search,
ChevronRight,
ShieldCheck,
Workflow,
Database,
Mail,
Server,
Shield,
BookOpen,
PanelLeft,
PanelRight,
ChevronDown,
Plus,
Filter,
Eye,
EyeOff,
SlidersHorizontal,
Columns3,
X,
ExternalLink,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { Progress } from "@/components/ui/progress";
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuLabel,
} from "@/components/ui/dropdown-menu";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Switch } from "@/components/ui/switch";
import { API_BASE } from "@/lib/api";
const sea = {
light: "#E6F7FB",
light2: "#F3FBFD",
mid: "#2A7F98",
dark: "#0D5C75",
textDark: "#0E2932",
};
const can = (user, perm) => new Set(user?.permissions || []).has(perm);
const Tag = ({ children }) => (
<Badge
className="px-3 py-1 text-xs border-0 rounded-full"
style={{ background: sea.light, color: sea.dark }}
>
{children}
</Badge>
);
const SidebarItem = ({ label, icon: Icon, active = false, badge }) => (
<button
className={`group w-full flex items-center gap-3 rounded-2xl px-4 py-3 text-left transition-all border ${
active ? "bg-white/70" : "bg-white/30 hover:bg-white/60"
}`}
style={{ borderColor: "#ffffff40", color: sea.textDark }}
>
<Icon className="w-5 h-5" />
<span className="font-medium grow">{label}</span>
{badge ? (
<span
className="text-xs rounded-full px-2 py-0.5"
style={{ background: sea.light, color: sea.dark }}
>
{badge}
</span>
) : null}
<ChevronRight className="w-4 h-4 transition-opacity opacity-0 group-hover:opacity-100" />
</button>
);
const KPI = ({ label, value, icon: Icon, onClick }) => (
<Card
onClick={onClick}
className="transition border-0 shadow-sm cursor-pointer rounded-2xl hover:shadow"
style={{ background: "white" }}
>
<CardContent className="p-5">
<div className="flex items-start justify-between">
<span className="text-sm opacity-70">{label}</span>
<div className="p-2 rounded-xl" style={{ background: sea.light }}>
<Icon className="w-5 h-5" style={{ color: sea.dark }} />
</div>
</div>
<div className="mt-3 text-3xl font-bold" style={{ color: sea.textDark }}>
{value}
</div>
<div className="mt-2">
<Progress value={Math.min(100, (value / 400) * 100)} />
</div>
</CardContent>
</Card>
);
function PreviewDrawer({ open, onClose, children }) {
return (
<div
className={`fixed top-0 right-0 h-full w-full sm:w-[420px] bg-white shadow-2xl transition-transform z-50 ${
open ? "translate-x-0" : "translate-x-full"
}`}
>
<div className="flex items-center justify-between p-4 border-b">
<div className="font-medium">รายละเอยด</div>
<Button variant="ghost" size="icon" onClick={onClose}>
<X className="w-5 h-5" />
</Button>
</div>
<div className="p-4 overflow-auto h-[calc(100%-56px)]">{children}</div>
</div>
);
}
export default function DashboardPage() {
const [user, setUser] = React.useState(null);
const [sidebarOpen, setSidebarOpen] = React.useState(true);
const [densityCompact, setDensityCompact] = React.useState(false);
const [showCols, setShowCols] = React.useState({
type: true,
id: true,
title: true,
status: true,
due: true,
owner: true,
actions: true,
});
const [previewOpen, setPreviewOpen] = React.useState(false);
const [filters, setFilters] = React.useState({
type: "All",
status: "All",
overdue: false,
});
const [activeQuery, setActiveQuery] = React.useState({});
React.useEffect(() => {
fetch(`${API_BASE}/auth/me`, { credentials: "include" })
.then((r) => (r.ok ? r.json() : null))
.then((data) => setUser(data?.user || null))
.catch(() => setUser(null));
}, []);
const quickLinks = [
{
label: "สร้าง RFA",
icon: FileText,
perm: "rfa:create",
href: "/rfas/new",
},
{
label: "อัปโหลด Drawing",
icon: Layers,
perm: "drawing:upload",
href: "/drawings/upload",
},
{
label: "สร้าง Transmittal",
icon: Send,
perm: "transmittal:create",
href: "/transmittals/new",
},
{
label: "บันทึกหนังสือสื่อสาร",
icon: Mail,
perm: "correspondence:create",
href: "/correspondences/new",
},
];
const nav = [
{ label: "แดชบอร์ด", icon: LayoutDashboard },
{ label: "Drawings", icon: Layers },
{ label: "RFAs", icon: FileText },
{ label: "Transmittals", icon: Send },
{ label: "Contracts & Volumes", icon: BookOpen },
{ label: "Correspondences", icon: Files },
{ label: "ผู้ใช้/บทบาท", icon: Users, perm: "users:manage" },
{ label: "Reports", icon: Activity },
{ label: "Workflow (n8n)", icon: Workflow, perm: "workflow:view" },
{ label: "Health", icon: Server, perm: "health:view" },
{ label: "Admin", icon: Settings, perm: "admin:view" },
];
const kpis = [
{
key: "rfa-pending",
label: "RFAs รออนุมัติ",
value: 12,
icon: FileText,
query: { type: "RFA", status: "pending" },
},
{
key: "drawings",
label: "แบบ (Drawings) ล่าสุด",
value: 326,
icon: Layers,
query: { type: "Drawing" },
},
{
key: "trans-month",
label: "Transmittals เดือนนี้",
value: 18,
icon: Send,
query: { type: "Transmittal", month: "current" },
},
{
key: "overdue",
label: "เกินกำหนด (Overdue)",
value: 5,
icon: Activity,
query: { overdue: true },
},
];
const recent = [
{
type: "RFA",
code: "RFA-LCP3-0012",
title: "ปรับปรุงรายละเอียดเสาเข็มท่าเรือ",
who: "สุรเชษฐ์ (Editor)",
when: "เมื่อวาน 16:40",
},
{
type: "Drawing",
code: "DWG-C-210A-Rev.3",
title: "แปลนโครงสร้างท่าเรือส่วนที่ 2",
who: "วรวิชญ์ (Admin)",
when: "วันนี้ 09:15",
},
{
type: "Transmittal",
code: "TR-2025-0916-04",
title: "ส่งแบบ Rebar Shop Drawing ชุด A",
who: "Supansa (Viewer)",
when: "16 ก.ย. 2025",
},
{
type: "Correspondence",
code: "CRSP-58",
title: "แจ้งเลื่อนประชุมตรวจแบบ",
who: "Kitti (Editor)",
when: "15 ก.ย. 2025",
},
];
const items = [
{
t: "RFA",
id: "RFA-LCP3-0013",
title: "ยืนยันรายละเอียดท่อระบายน้ำ",
status: "Pending",
due: "20 ก.ย. 2025",
owner: "คุณแดง",
},
{
t: "Drawing",
id: "DWG-S-115-Rev.1",
title: "Section เสาเข็มพื้นที่ส่วนที่ 1",
status: "Review",
due: "19 ก.ย. 2025",
owner: "วิทยา",
},
{
t: "Transmittal",
id: "TR-2025-0915-03",
title: "ส่งแบบโครงสร้างท่าเรือ ชุด B",
status: "Sent",
due: "—",
owner: "สุธิดา",
},
];
const visibleItems = items.filter((r) => {
if (filters.type !== "All" && r.t !== filters.type) return false;
if (filters.status !== "All" && r.status !== filters.status) return false;
if (filters.overdue && r.due === "—") return false;
return true;
});
const onKpiClick = (q) => {
setActiveQuery(q);
if (q?.type) setFilters((f) => ({ ...f, type: q.type }));
if (q?.overdue) setFilters((f) => ({ ...f, overdue: true }));
};
return (
<TooltipProvider>
<div
className="min-h-screen"
style={{
background: `linear-gradient(180deg, ${sea.light2} 0%, ${sea.light} 100%)`,
}}
>
<header
className="sticky top-0 z-40 border-b backdrop-blur-md"
style={{
borderColor: "#ffffff66",
background: "rgba(230,247,251,0.7)",
}}
>
<div className="flex items-center gap-3 px-4 py-2 mx-auto max-w-7xl">
<button
className="flex items-center justify-center shadow-sm h-9 w-9 rounded-2xl"
style={{ background: sea.dark }}
onClick={() => setSidebarOpen((v) => !v)}
aria-label={sidebarOpen ? "ซ่อนแถบด้านข้าง" : "แสดงแถบด้านข้าง"}
>
{sidebarOpen ? (
<PanelLeft className="w-5 h-5 text-white" />
) : (
<PanelRight className="w-5 h-5 text-white" />
)}
</button>
<div>
<div className="text-xs opacity-70">
Document Management System
</div>
<div className="font-semibold" style={{ color: sea.textDark }}>
โครงการพฒนาทาเรอแหลมฉบ ระยะท 3 วนท 14
</div>
</div>
<Tag>Phase 3</Tag>
<Tag>Port Infrastructure</Tag>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="flex items-center gap-2 ml-auto rounded-2xl btn-sea">
System <ChevronDown className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-56">
<DropdownMenuLabel>ระบบ</DropdownMenuLabel>
{can(user, "admin:view") && (
<DropdownMenuItem>
<Settings className="w-4 h-4 mr-2" /> Admin
</DropdownMenuItem>
)}
{can(user, "users:manage") && (
<DropdownMenuItem>
<Users className="w-4 h-4 mr-2" /> ใช/บทบาท
</DropdownMenuItem>
)}
{can(user, "health:view") && (
<DropdownMenuItem asChild>
<a href="/health" className="flex items-center w-full">
<Server className="w-4 h-4 mr-2" /> Health{" "}
<ExternalLink className="w-3 h-3 ml-auto" />
</a>
</DropdownMenuItem>
)}
{can(user, "workflow:view") && (
<DropdownMenuItem asChild>
<a href="/workflow" className="flex items-center w-full">
<Workflow className="w-4 h-4 mr-2" /> Workflow (n8n){" "}
<ExternalLink className="w-3 h-3 ml-auto" />
</a>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="ml-2 rounded-2xl btn-sea">
<Plus className="w-4 h-4 mr-1" /> New
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{quickLinks.map(({ label, icon: Icon, perm, href }) =>
can(user, perm) ? (
<DropdownMenuItem key={label} asChild>
<Link href={href} className="flex items-center">
<Icon className="w-4 h-4 mr-2" />
{label}
</Link>
</DropdownMenuItem>
) : (
<Tooltip key={label}>
<TooltipTrigger asChild>
<div className="px-2 py-1.5 text-sm opacity-40 cursor-not-allowed flex items-center">
<Icon className="w-4 h-4 mr-2" />
{label}
</div>
</TooltipTrigger>
<TooltipContent>
ไมทธใชงาน ({perm})
</TooltipContent>
</Tooltip>
)
)}
<DropdownMenuSeparator />
<DropdownMenuItem>
<Layers className="w-4 h-4 mr-2" /> Import / Bulk upload
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
<div className="grid grid-cols-12 gap-6 px-4 py-6 mx-auto max-w-7xl">
{sidebarOpen && (
<aside className="col-span-12 lg:col-span-3 xl:col-span-3">
<div
className="p-4 border rounded-3xl"
style={{
background: "rgba(255,255,255,0.7)",
borderColor: "#ffffff66",
}}
>
<div className="flex items-center gap-2 mb-3">
<ShieldCheck
className="w-5 h-5"
style={{ color: sea.dark }}
/>
<div className="text-sm">
RBAC:{" "}
<span className="font-medium">{user?.role || "—"}</span>
</div>
</div>
<div className="relative mb-3">
<Search className="absolute w-4 h-4 -translate-y-1/2 left-3 top-1/2 opacity-70" />
<Input
placeholder="ค้นหา RFA / Drawing / Transmittal / Code…"
className="bg-white border-0 pl-9 rounded-2xl"
/>
</div>
<div
className="p-3 mb-3 border rounded-2xl"
style={{ borderColor: "#eef6f8", background: "#ffffffaa" }}
>
<div className="mb-2 text-xs font-medium">วกรอง</div>
<div className="grid grid-cols-2 gap-2">
<select
className="p-2 text-sm border rounded-xl"
value={filters.type}
onChange={(e) =>
setFilters((f) => ({ ...f, type: e.target.value }))
}
>
<option>All</option>
<option>RFA</option>
<option>Drawing</option>
<option>Transmittal</option>
<option>Correspondence</option>
</select>
<select
className="p-2 text-sm border rounded-xl"
value={filters.status}
onChange={(e) =>
setFilters((f) => ({ ...f, status: e.target.value }))
}
>
<option>All</option>
<option>Pending</option>
<option>Review</option>
<option>Sent</option>
</select>
<label className="flex items-center col-span-2 gap-2 text-sm">
<Switch
checked={filters.overdue}
onCheckedChange={(v) =>
setFilters((f) => ({ ...f, overdue: v }))
}
/>{" "}
แสดงเฉพาะ Overdue
</label>
</div>
<div className="flex gap-2 mt-2">
<Button
size="sm"
variant="outline"
className="rounded-xl"
style={{ borderColor: sea.mid, color: sea.dark }}
>
<Filter className="w-4 h-4 mr-1" />
Apply
</Button>
<Button
size="sm"
variant="ghost"
className="rounded-xl"
onClick={() =>
setFilters({
type: "All",
status: "All",
overdue: false,
})
}
>
Reset
</Button>
</div>
</div>
<div className="space-y-2">
{nav
.filter((item) => !item.perm || can(user, item.perm))
.map((n, i) => (
<SidebarItem
key={n.label}
label={n.label}
icon={n.icon}
active={i === 0}
badge={n.label === "RFAs" ? 12 : undefined}
/>
))}
</div>
<div className="flex items-center gap-2 mt-5 text-xs opacity-70">
<Database className="w-4 h-4" /> dms_db MariaDB 10.11
</div>
</div>
</aside>
)}
<main
className={`col-span-12 ${
sidebarOpen ? "lg:col-span-9 xl:col-span-9" : ""
} space-y-6`}
>
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.05, duration: 0.4 }}
>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{kpis.map((k) => (
<KPI key={k.key} {...k} onClick={() => onKpiClick(k.query)} />
))}
</div>
</motion.div>
<div className="flex items-center justify-between">
<div className="text-sm opacity-70">
ผลลพธจากตวกรอง: {filters.type}/{filters.status}
{filters.overdue ? " • Overdue" : ""}
</div>
<div className="flex items-center gap-2">
<Button
size="sm"
variant="outline"
className="rounded-xl"
style={{ borderColor: sea.mid, color: sea.dark }}
onClick={() => setDensityCompact((v) => !v)}
>
<SlidersHorizontal className="w-4 h-4 mr-1" /> Density:{" "}
{densityCompact ? "Compact" : "Comfort"}
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="sm"
variant="outline"
className="rounded-xl"
style={{ borderColor: sea.mid, color: sea.dark }}
>
<Columns3 className="w-4 h-4 mr-1" /> Columns
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{Object.keys(showCols).map((key) => (
<DropdownMenuItem
key={key}
onClick={() =>
setShowCols((s) => ({ ...s, [key]: !s[key] }))
}
>
{showCols[key] ? (
<Eye className="w-4 h-4 mr-2" />
) : (
<EyeOff className="w-4 h-4 mr-2" />
)}
{key}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<Card className="border-0 rounded-2xl">
<CardContent className="p-0">
<div className="overflow-x-auto">
<table
className={`min-w-full text-sm ${
densityCompact ? "[&_*]:py-1" : ""
}`}
>
<thead
className="sticky top-[56px] z-10"
style={{
background: "white",
borderBottom: "1px solid #efefef",
}}
>
<tr className="text-left">
{showCols.type && <th className="px-3 py-2">ประเภท</th>}
{showCols.id && <th className="px-3 py-2">รห</th>}
{showCols.title && (
<th className="px-3 py-2">อเรอง</th>
)}
{showCols.status && (
<th className="px-3 py-2">สถานะ</th>
)}
{showCols.due && (
<th className="px-3 py-2">กำหนดส</th>
)}
{showCols.owner && (
<th className="px-3 py-2">บผดชอบ</th>
)}
{showCols.actions && (
<th className="px-3 py-2">ดการ</th>
)}
</tr>
</thead>
<tbody>
{visibleItems.length === 0 && (
<tr>
<td
className="px-3 py-8 text-center opacity-70"
colSpan={7}
>
ไมพบรายการตามตวกรองทเลอก
</td>
</tr>
)}
{visibleItems.map((row) => (
<tr
key={row.id}
className="border-b cursor-pointer hover:bg-gray-50/50"
style={{ borderColor: "#f3f3f3" }}
onClick={() => setPreviewOpen(true)}
>
{showCols.type && (
<td className="px-3 py-2">{row.t}</td>
)}
{showCols.id && (
<td className="px-3 py-2 font-mono">{row.id}</td>
)}
{showCols.title && (
<td className="px-3 py-2">{row.title}</td>
)}
{showCols.status && (
<td className="px-3 py-2">
<Tag>{row.status}</Tag>
</td>
)}
{showCols.due && (
<td className="px-3 py-2">{row.due}</td>
)}
{showCols.owner && (
<td className="px-3 py-2">{row.owner}</td>
)}
{showCols.actions && (
<td className="px-3 py-2">
<div className="flex gap-2">
<Button
size="sm"
className="rounded-xl btn-sea"
>
เป
</Button>
<Button
size="sm"
variant="outline"
className="rounded-xl"
style={{
borderColor: sea.mid,
color: sea.dark,
}}
>
Assign
</Button>
</div>
</td>
)}
</tr>
))}
</tbody>
</table>
</div>
<div
className="px-4 py-2 text-xs border-t opacity-70"
style={{ borderColor: "#efefef" }}
>
เคลดล: ใช / เลอนแถว, Enter เป, /
</div>
</CardContent>
</Card>
<Tabs defaultValue="overview" className="w-full">
<TabsList
className="border rounded-2xl bg-white/80"
style={{ borderColor: "#ffffff80" }}
>
<TabsTrigger value="overview">ภาพรวม</TabsTrigger>
<TabsTrigger value="reports">รายงาน</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="mt-4 space-y-4">
<div className="grid gap-4 lg:grid-cols-5">
<Card className="border-0 rounded-2xl lg:col-span-3">
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div
className="font-semibold"
style={{ color: sea.textDark }}
>
สถานะโครงการ
</div>
<Tag>Phase 3 วนท 14</Tag>
</div>
<div className="mt-4 space-y-3">
<div>
<div className="text-sm opacity-70">
ความคบหนาโดยรวม
</div>
<Progress value={62} />
</div>
<div className="grid grid-cols-3 gap-3">
<div
className="p-4 border rounded-xl"
style={{
background: sea.light,
borderColor: sea.light,
}}
>
<div className="text-xs opacity-70">วนท 1</div>
<div className="text-lg font-semibold">
เสร 70%
</div>
</div>
<div
className="p-4 border rounded-xl"
style={{
background: sea.light,
borderColor: sea.light,
}}
>
<div className="text-xs opacity-70">วนท 2</div>
<div className="text-lg font-semibold">
เสร 58%
</div>
</div>
<div
className="p-4 border rounded-xl"
style={{
background: sea.light,
borderColor: sea.light,
}}
>
<div className="text-xs opacity-70">
วนท 34
</div>
<div className="text-lg font-semibold">
เสร 59%
</div>
</div>
</div>
</div>
</CardContent>
</Card>
<Card className="border-0 rounded-2xl lg:col-span-2">
<CardContent className="p-5 space-y-3">
<div className="flex items-center justify-between">
<div
className="font-semibold"
style={{ color: sea.textDark }}
>
System Health
</div>
<Tag>QNAP Container Station</Tag>
</div>
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2">
<Server className="w-4 h-4" /> Nginx Reverse Proxy{" "}
<span
className="ml-auto font-medium"
style={{ color: sea.dark }}
>
Healthy
</span>
</div>
<div className="flex items-center gap-2">
<Database className="w-4 h-4" /> MariaDB 10.11{" "}
<span
className="ml-auto font-medium"
style={{ color: sea.dark }}
>
OK
</span>
</div>
<div className="flex items-center gap-2">
<Workflow className="w-4 h-4" /> n8n (Postgres){" "}
<span
className="ml-auto font-medium"
style={{ color: sea.dark }}
>
OK
</span>
</div>
<div className="flex items-center gap-2">
<Shield className="w-4 h-4" /> RBAC Enforcement{" "}
<span
className="ml-auto font-medium"
style={{ color: sea.dark }}
>
Enabled
</span>
</div>
</div>
<div
className="pt-2 border-t"
style={{ borderColor: "#eeeeee" }}
>
<Button
variant="outline"
className="rounded-xl"
style={{ borderColor: sea.mid, color: sea.dark }}
>
เปดหน /health
</Button>
</div>
</CardContent>
</Card>
</div>
<Card className="border-0 rounded-2xl">
<CardContent className="p-5">
<div className="flex items-center justify-between mb-3">
<div
className="font-semibold"
style={{ color: sea.textDark }}
>
จกรรมลาส
</div>
<div className="flex gap-2">
<Tag>Admin</Tag>
<Tag>Editor</Tag>
<Tag>Viewer</Tag>
</div>
</div>
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
{recent.map((r) => (
<div
key={r.code}
className="p-4 transition border rounded-2xl hover:shadow-sm"
style={{
background: "white",
borderColor: "#efefef",
}}
>
<div className="text-xs opacity-70">
{r.type} {r.code}
</div>
<div
className="mt-1 font-medium"
style={{ color: sea.textDark }}
>
{r.title}
</div>
<div className="mt-2 text-xs opacity-70">{r.who}</div>
<div className="text-xs opacity-70">{r.when}</div>
</div>
))}
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="reports" className="mt-4">
<div className="grid gap-4 lg:grid-cols-2">
<Card className="border-0 rounded-2xl">
<CardContent className="p-5">
<div
className="mb-2 font-semibold"
style={{ color: sea.textDark }}
>
Report A: RFA Drawings Revisions
</div>
<div className="text-sm opacity-70">
รวมท Drawing Revision + Code
</div>
<div className="mt-3">
<Button className="rounded-2xl btn-sea">
Export CSV
</Button>
</div>
</CardContent>
</Card>
<Card className="border-0 rounded-2xl">
<CardContent className="p-5">
<div
className="mb-2 font-semibold"
style={{ color: sea.textDark }}
>
Report B: ไทมไลน RFA vs Drawing Rev
</div>
<div className="text-sm opacity-70">
Query #2 กำหนดไว
</div>
<div className="mt-3">
<Button className="rounded-2xl btn-sea">
รายงาน
</Button>
</div>
</CardContent>
</Card>
</div>
</TabsContent>
</Tabs>
<div className="py-6 text-xs text-center opacity-70">
Sea-themed Dashboard Sidebar อนได RBAC แสดง/อน Faceted
search KPI click-through Preview drawer Column
visibility/Density
</div>
</main>
</div>
<PreviewDrawer open={previewOpen} onClose={() => setPreviewOpen(false)}>
<div className="space-y-2 text-sm">
<div>
<span className="opacity-70">รห:</span> RFA-LCP3-0013
</div>
<div>
<span className="opacity-70">อเรอง:</span>{" "}
นยนรายละเอยดทอระบายน
</div>
<div>
<span className="opacity-70">สถานะ:</span> <Tag>Pending</Tag>
</div>
<div>
<span className="opacity-70">แนบไฟล:</span> 2 (PDF, DWG)
</div>
<div className="flex gap-2 pt-2">
{can(user, "rfa:create") && (
<Button className="btn-sea rounded-xl">แกไข</Button>
)}
<Button
variant="outline"
className="rounded-xl"
style={{ borderColor: sea.mid, color: sea.dark }}
>
เปดเตมหน
</Button>
</div>
</div>
</PreviewDrawer>
<style jsx global>{`
.btn-sea {
background: ${sea.dark};
}
.btn-sea:hover {
background: ${sea.mid};
}
.menu-sea {
background: ${sea.dark};
}
.menu-sea:hover {
background: ${sea.mid};
}
`}</style>
</div>
</TooltipProvider>
);
}