155 lines
6.8 KiB
JavaScript
Executable File
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>
|
|
);
|
|
} |