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

978 lines
38 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="rounded-full px-3 py-1 text-xs border-0"
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="h-5 w-5" />
<span className="grow font-medium">{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="h-4 w-4 opacity-0 group-hover:opacity-100 transition-opacity" />
</button>
);
const KPI = ({ label, value, icon: Icon, onClick }) => (
<Card
onClick={onClick}
className="rounded-2xl shadow-sm border-0 cursor-pointer hover:shadow transition"
style={{ background: "white" }}
>
<CardContent className="p-5">
<div className="flex items-start justify-between">
<span className="text-sm opacity-70">{label}</span>
<div className="rounded-xl p-2" style={{ background: sea.light }}>
<Icon className="h-5 w-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="h-5 w-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 backdrop-blur-md border-b"
style={{
borderColor: "#ffffff66",
background: "rgba(230,247,251,0.7)",
}}
>
<div className="mx-auto max-w-7xl px-4 py-2 flex items-center gap-3">
<button
className="h-9 w-9 rounded-2xl flex items-center justify-center shadow-sm"
style={{ background: sea.dark }}
onClick={() => setSidebarOpen((v) => !v)}
aria-label={sidebarOpen ? "ซ่อนแถบด้านข้าง" : "แสดงแถบด้านข้าง"}
>
{sidebarOpen ? (
<PanelLeft className="h-5 w-5 text-white" />
) : (
<PanelRight className="h-5 w-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="ml-auto rounded-2xl btn-sea flex items-center gap-2">
System <ChevronDown className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-56">
<DropdownMenuLabel>ระบบ</DropdownMenuLabel>
{can(user, "admin:view") && (
<DropdownMenuItem>
<Settings className="h-4 w-4 mr-2" /> Admin
</DropdownMenuItem>
)}
{can(user, "users:manage") && (
<DropdownMenuItem>
<Users className="h-4 w-4 mr-2" /> ใช/บทบาท
</DropdownMenuItem>
)}
{can(user, "health:view") && (
<DropdownMenuItem asChild>
<a href="/health" className="flex items-center w-full">
<Server className="h-4 w-4 mr-2" /> Health{" "}
<ExternalLink className="h-3 w-3 ml-auto" />
</a>
</DropdownMenuItem>
)}
{can(user, "workflow:view") && (
<DropdownMenuItem asChild>
<a href="/workflow" className="flex items-center w-full">
<Workflow className="h-4 w-4 mr-2" /> Workflow (n8n){" "}
<ExternalLink className="h-3 w-3 ml-auto" />
</a>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="rounded-2xl btn-sea ml-2">
<Plus className="h-4 w-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="h-4 w-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="h-4 w-4 mr-2" />
{label}
</div>
</TooltipTrigger>
<TooltipContent>
ไมทธใชงาน ({perm})
</TooltipContent>
</Tooltip>
)
)}
<DropdownMenuSeparator />
<DropdownMenuItem>
<Layers className="h-4 w-4 mr-2" /> Import / Bulk upload
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
<div className="mx-auto max-w-7xl px-4 py-6 grid grid-cols-12 gap-6">
{sidebarOpen && (
<aside className="col-span-12 lg:col-span-3 xl:col-span-3">
<div
className="rounded-3xl p-4 border"
style={{
background: "rgba(255,255,255,0.7)",
borderColor: "#ffffff66",
}}
>
<div className="mb-3 flex items-center gap-2">
<ShieldCheck
className="h-5 w-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 left-3 top-1/2 -translate-y-1/2 h-4 w-4 opacity-70" />
<Input
placeholder="ค้นหา RFA / Drawing / Transmittal / Code…"
className="pl-9 rounded-2xl border-0 bg-white"
/>
</div>
<div
className="rounded-2xl p-3 border mb-3"
style={{ borderColor: "#eef6f8", background: "#ffffffaa" }}
>
<div className="text-xs font-medium mb-2">วกรอง</div>
<div className="grid grid-cols-2 gap-2">
<select
className="rounded-xl border p-2 text-sm"
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="rounded-xl border p-2 text-sm"
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="col-span-2 flex items-center gap-2 text-sm">
<Switch
checked={filters.overdue}
onCheckedChange={(v) =>
setFilters((f) => ({ ...f, overdue: v }))
}
/>{" "}
แสดงเฉพาะ Overdue
</label>
</div>
<div className="mt-2 flex gap-2">
<Button
size="sm"
variant="outline"
className="rounded-xl"
style={{ borderColor: sea.mid, color: sea.dark }}
>
<Filter className="h-4 w-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="mt-5 text-xs opacity-70 flex items-center gap-2">
<Database className="h-4 w-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 sm:grid-cols-2 lg:grid-cols-4 gap-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="h-4 w-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="h-4 w-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="h-4 w-4 mr-2" />
) : (
<EyeOff className="h-4 w-4 mr-2" />
)}
{key}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<Card className="rounded-2xl border-0">
<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="py-2 px-3">ประเภท</th>}
{showCols.id && <th className="py-2 px-3">รห</th>}
{showCols.title && (
<th className="py-2 px-3">อเรอง</th>
)}
{showCols.status && (
<th className="py-2 px-3">สถานะ</th>
)}
{showCols.due && (
<th className="py-2 px-3">กำหนดส</th>
)}
{showCols.owner && (
<th className="py-2 px-3">บผดชอบ</th>
)}
{showCols.actions && (
<th className="py-2 px-3">ดการ</th>
)}
</tr>
</thead>
<tbody>
{visibleItems.length === 0 && (
<tr>
<td
className="py-8 px-3 text-center opacity-70"
colSpan={7}
>
ไมพบรายการตามตวกรองทเลอก
</td>
</tr>
)}
{visibleItems.map((row) => (
<tr
key={row.id}
className="border-b hover:bg-gray-50/50 cursor-pointer"
style={{ borderColor: "#f3f3f3" }}
onClick={() => setPreviewOpen(true)}
>
{showCols.type && (
<td className="py-2 px-3">{row.t}</td>
)}
{showCols.id && (
<td className="py-2 px-3 font-mono">{row.id}</td>
)}
{showCols.title && (
<td className="py-2 px-3">{row.title}</td>
)}
{showCols.status && (
<td className="py-2 px-3">
<Tag>{row.status}</Tag>
</td>
)}
{showCols.due && (
<td className="py-2 px-3">{row.due}</td>
)}
{showCols.owner && (
<td className="py-2 px-3">{row.owner}</td>
)}
{showCols.actions && (
<td className="py-2 px-3">
<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 opacity-70 border-t"
style={{ borderColor: "#efefef" }}
>
เคลดล: ใช / เลอนแถว, Enter เป, /
</div>
</CardContent>
</Card>
<Tabs defaultValue="overview" className="w-full">
<TabsList
className="rounded-2xl border 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 lg:grid-cols-5 gap-4">
<Card className="rounded-2xl border-0 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="rounded-xl p-4 border"
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="rounded-xl p-4 border"
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="rounded-xl p-4 border"
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="rounded-2xl border-0 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="h-4 w-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="h-4 w-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="h-4 w-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="h-4 w-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="rounded-2xl border-0">
<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 md:grid-cols-2 xl:grid-cols-4 gap-3">
{recent.map((r) => (
<div
key={r.code}
className="rounded-2xl p-4 border hover:shadow-sm transition"
style={{
background: "white",
borderColor: "#efefef",
}}
>
<div className="text-xs opacity-70">
{r.type} {r.code}
</div>
<div
className="font-medium mt-1"
style={{ color: sea.textDark }}
>
{r.title}
</div>
<div className="text-xs mt-2 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 lg:grid-cols-2 gap-4">
<Card className="rounded-2xl border-0">
<CardContent className="p-5">
<div
className="font-semibold mb-2"
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="rounded-2xl border-0">
<CardContent className="p-5">
<div
className="font-semibold mb-2"
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="text-xs opacity-70 text-center py-6">
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="pt-2 flex gap-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>
);
}