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

155 lines
6.8 KiB
JavaScript
Executable File

// frontend/app/(protected)/dashboard/page.jsx
'use client';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Activity, File, FilePlus, ArrowRight, BellDot, Settings } from 'lucide-react';
import { api } from '@/lib/api';
import { useAuth } from '@/lib/auth';
import { can } from '@/lib/rbac';
export default function DashboardPage() {
const { user } = useAuth();
const [stats, setStats] = useState(null);
const [myTasks, setMyTasks] = useState([]);
const [recentActivity, setRecentActivity] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
// เรียก API ที่จำเป็นสำหรับ Dashboard พร้อมกัน
// หมายเหตุ: Endpoint เหล่านี้อาจจะต้องสร้างเพิ่มเติมในฝั่ง Backend
const [statsRes, tasksRes, activityRes] = await Promise.all([
api.get('/dashboard/stats').catch(e => ({ data: { totalDocuments: 0, newThisWeek: 0, pendingRfas: 0 }})), // สมมติ endpoint สำหรับ KPI
api.get('/dashboard/my-tasks').catch(e => ({ data: [] })), // สมมติ endpoint สำหรับ Action Items
api.get('/dashboard/recent-activity').catch(e => ({ data: [] })), // สมมติ endpoint สำหรับ Recent Activity
]);
setStats(statsRes.data);
setMyTasks(tasksRes.data);
setRecentActivity(activityRes.data);
} catch (error) {
console.error("Failed to fetch dashboard data:", error);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) {
// อาจจะใช้ Skeleton UI ที่นี่เพื่อให้ UX ดีขึ้น
return <div className="text-center">Loading Dashboard...</div>;
}
return (
<div className="space-y-6">
{/* ส่วน Header และ Quick Access Buttons */}
<div className="flex flex-wrap items-center justify-between gap-4">
<div>
<h1 className="text-3xl font-bold">Dashboard</h1>
<p className="text-muted-foreground">Welcome back, {user?.username || 'User'}!</p>
</div>
<div className="flex gap-2">
<Button asChild>
<Link href="/correspondences/new">
<FilePlus className="w-4 h-4 mr-2" /> New Correspondence
</Link>
</Button>
{/* ปุ่ม Admin Settings จะแสดงเมื่อมีสิทธิ์ 'manage_users' เท่านั้น */}
{user && can(user, 'manage_users') && (
<Button asChild variant="outline">
<Link href="/admin/users">
<Settings className="w-4 h-4 mr-2" /> Admin Settings
</Link>
</Button>
)}
</div>
</div>
{/* ส่วน Key Metrics (KPIs) */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0">
<CardTitle className="text-sm font-medium">Total Documents</CardTitle>
<File className="w-4 h-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stats?.totalDocuments || 0}</div>
<p className="text-xs text-muted-foreground">+{stats?.newThisWeek || 0} this week</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0">
<CardTitle className="text-sm font-medium">Pending RFAs</CardTitle>
<BellDot className="w-4 h-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stats?.pendingRfas || 0}</div>
<p className="text-xs text-muted-foreground">Require your attention</p>
</CardContent>
</Card>
{/* สามารถเพิ่ม Card อื่นๆ ตามต้องการ */}
</div>
{/* ส่วน Action Items และ Recent Activity */}
<div className="grid gap-6 md:grid-cols-1 lg:grid-cols-3">
{/* Action Items */}
<Card className="lg:col-span-1">
<CardHeader>
<CardTitle>My Action Items</CardTitle>
<CardDescription>Tasks that require your immediate attention.</CardDescription>
</CardHeader>
<CardContent className="pl-6">
{myTasks && myTasks.length > 0 ? (
<ul className="space-y-4">
{myTasks.map(task => (
<li key={task.id} className="flex items-start">
<ArrowRight className="flex-shrink-0 w-4 h-4 mt-1 mr-3 text-primary" />
<Link href={task.link || '#'} className="text-sm hover:underline">
{task.title}
</Link>
</li>
))}
</ul>
) : (
<p className="text-sm italic text-muted-foreground">No pending tasks. You're all caught up!</p>
)}
</CardContent>
</Card>
{/* Recent Activity */}
<Card className="lg:col-span-2">
<CardHeader>
<CardTitle>Recent Project Activity</CardTitle>
<CardDescription>Latest updates from the team.</CardDescription>
</CardHeader>
<CardContent>
{recentActivity && recentActivity.length > 0 ? (
<ul className="space-y-4">
{recentActivity.map(activity => (
<li key={activity.id} className="flex items-start">
<Activity className="flex-shrink-0 w-4 h-4 mt-1 mr-3 text-muted-foreground" />
<div>
<p className="text-sm" dangerouslySetInnerHTML={{ __html: activity.description }}></p>
<p className="text-xs text-muted-foreground">{new Date(activity.timestamp).toLocaleString()}</p>
</div>
</li>
))}
</ul>
) : (
<p className="text-sm italic text-muted-foreground">No recent activity.</p>
)}
</CardContent>
</Card>
</div>
</div>
);
}