Apply .gitignore cleanup
This commit is contained in:
308
frontend/app/(protected)/dashboard/page.jsx
Executable file → Normal file
308
frontend/app/(protected)/dashboard/page.jsx
Executable file → Normal file
@@ -1,155 +1,155 @@
|
||||
// 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>
|
||||
);
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user