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