447 lines
25 KiB
TypeScript
447 lines
25 KiB
TypeScript
"use client"
|
||
|
||
import { useState, useEffect } from "react"
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||
import { Badge } from "@/components/ui/badge"
|
||
import { Button } from "@/components/ui/button"
|
||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from "recharts"
|
||
import {
|
||
TrendingUp,
|
||
TrendingDown,
|
||
Calendar,
|
||
AlertTriangle,
|
||
Target,
|
||
Users,
|
||
DollarSign,
|
||
Activity,
|
||
Zap,
|
||
Award,
|
||
} from "lucide-react"
|
||
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"
|
||
|
||
// 使用真實資料庫數據
|
||
import { KPI } from '@/lib/database'
|
||
|
||
// 預設 KPI 數據(當資料庫無數據時使用)
|
||
const defaultKpiData = [
|
||
{ id: '1', name: "營收成長率", current_value: 85, target_value: 100, category: "financial", weight: 30, color: "emerald" },
|
||
{ id: '2', name: "團隊滿意度", current_value: 92, target_value: 90, category: "team", weight: 25, color: "blue" },
|
||
{ id: '3', name: "市場佔有率", current_value: 68, target_value: 75, category: "operational", weight: 20, color: "purple" },
|
||
{ id: '4', name: "創新指數", current_value: 45, target_value: 80, category: "innovation", weight: 25, color: "orange" },
|
||
]
|
||
|
||
const teamRankingData = [
|
||
{ name: "業務部", performance: 95, color: "#10b981" },
|
||
{ name: "行銷部", performance: 88, color: "#3b82f6" },
|
||
{ name: "產品部", performance: 82, color: "#8b5cf6" },
|
||
{ name: "營運部", performance: 76, color: "#f59e0b" },
|
||
{ name: "人資部", performance: 71, color: "#ef4444" },
|
||
]
|
||
|
||
const reviewReminders = [
|
||
{
|
||
id: 1,
|
||
employee: "陳雅雯",
|
||
role: "業務副總",
|
||
dueDate: "2024-02-15",
|
||
type: "季度審查",
|
||
priority: "high",
|
||
color: "red",
|
||
},
|
||
{
|
||
id: 2,
|
||
employee: "王志明",
|
||
role: "技術長",
|
||
dueDate: "2024-02-18",
|
||
type: "一對一面談",
|
||
priority: "medium",
|
||
color: "yellow",
|
||
},
|
||
{
|
||
id: 3,
|
||
employee: "李美玲",
|
||
role: "行銷長",
|
||
dueDate: "2024-02-20",
|
||
type: "目標設定",
|
||
priority: "low",
|
||
color: "green",
|
||
},
|
||
]
|
||
|
||
const chartConfig = {
|
||
performance: {
|
||
label: "Performance %",
|
||
color: "hsl(var(--chart-1))",
|
||
},
|
||
}
|
||
|
||
function CircularProgress({ value, size = 120, color = "blue" }: { value: number; size?: number; color?: string }) {
|
||
const radius = (size - 20) / 2
|
||
const circumference = 2 * Math.PI * radius
|
||
const strokeDasharray = circumference
|
||
const strokeDashoffset = circumference - (value / 100) * circumference
|
||
|
||
const colorMap = {
|
||
emerald: value >= 70 ? "#10b981" : "#ef4444",
|
||
blue: value >= 70 ? "#3b82f6" : "#ef4444",
|
||
purple: value >= 70 ? "#8b5cf6" : "#ef4444",
|
||
orange: value >= 70 ? "#f59e0b" : "#ef4444",
|
||
}
|
||
|
||
return (
|
||
<div className="relative" style={{ width: size, height: size }}>
|
||
<svg width={size} height={size} className="transform -rotate-90">
|
||
<circle cx={size / 2} cy={size / 2} r={radius} stroke="#e5e7eb" strokeWidth="8" fill="none" />
|
||
<circle
|
||
cx={size / 2}
|
||
cy={size / 2}
|
||
r={radius}
|
||
stroke={colorMap[color as keyof typeof colorMap]}
|
||
strokeWidth="8"
|
||
fill="none"
|
||
strokeDasharray={strokeDasharray}
|
||
strokeDashoffset={strokeDashoffset}
|
||
strokeLinecap="round"
|
||
className="transition-all duration-1000 ease-out"
|
||
/>
|
||
</svg>
|
||
<div className="absolute inset-0 flex items-center justify-center">
|
||
<span className="text-2xl font-bold text-gray-800">{value}%</span>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default function Dashboard() {
|
||
const [mounted, setMounted] = useState(false)
|
||
const [kpiData, setKpiData] = useState<KPI[]>([])
|
||
const [loading, setLoading] = useState(true)
|
||
const [error, setError] = useState<string | null>(null)
|
||
|
||
useEffect(() => {
|
||
setMounted(true)
|
||
fetchKPIData()
|
||
}, [])
|
||
|
||
const fetchKPIData = async () => {
|
||
try {
|
||
setLoading(true)
|
||
// 使用預設用戶 ID,實際應用中應該從認證系統獲取
|
||
const response = await fetch('/api/kpi?userId=user_admin')
|
||
if (response.ok) {
|
||
const data = await response.json()
|
||
setKpiData(data)
|
||
} else {
|
||
// 如果 API 失敗,使用預設數據
|
||
setKpiData(defaultKpiData as any)
|
||
}
|
||
} catch (error) {
|
||
console.error('獲取 KPI 數據失敗:', error)
|
||
setError('無法載入 KPI 數據')
|
||
// 使用預設數據
|
||
setKpiData(defaultKpiData as any)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
if (!mounted) return null
|
||
|
||
// 轉換數據格式以匹配 UI 需求
|
||
const displayKpiData = kpiData.map(kpi => ({
|
||
...kpi,
|
||
current: kpi.current_value,
|
||
target: kpi.target_value,
|
||
color: kpi.category === 'financial' ? 'emerald' :
|
||
kpi.category === 'team' ? 'blue' :
|
||
kpi.category === 'operational' ? 'purple' : 'orange'
|
||
}))
|
||
|
||
const underperformingKPIs = displayKpiData.filter((kpi) => kpi.current < 70)
|
||
const overallScore = Math.round(displayKpiData.reduce((acc, kpi) => acc + (kpi.current * kpi.weight) / 100, 0))
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 p-3 sm:p-6">
|
||
<div className="max-w-7xl mx-auto space-y-4 sm:space-y-6">
|
||
{/* Header */}
|
||
<div className="flex flex-col space-y-4 sm:flex-row sm:items-center sm:justify-between sm:space-y-0">
|
||
<div className="flex flex-col sm:flex-row sm:items-center space-y-2 sm:space-y-0 sm:space-x-4">
|
||
<img
|
||
src="data:image/png;base64,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"
|
||
alt="公司 Logo"
|
||
className="h-10 w-auto sm:h-12 md:hidden"
|
||
/>
|
||
<div>
|
||
<h1 className="text-xl sm:text-2xl lg:text-3xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||
高階主管績效管理儀表板
|
||
</h1>
|
||
<p className="text-sm sm:text-base text-gray-600 mt-1">策略導向 KPI 管理與審查系統</p>
|
||
</div>
|
||
</div>
|
||
<div className="flex flex-col sm:flex-row items-start sm:items-center space-y-2 sm:space-y-0 sm:space-x-4">
|
||
<Badge
|
||
variant="outline"
|
||
className={`text-sm sm:text-lg px-3 py-1 sm:px-4 sm:py-2 border-2 ${
|
||
overallScore >= 80
|
||
? "border-emerald-400 text-emerald-700 bg-emerald-50"
|
||
: overallScore >= 60
|
||
? "border-yellow-400 text-yellow-700 bg-yellow-50"
|
||
: "border-red-400 text-red-700 bg-red-50"
|
||
}`}
|
||
>
|
||
整體評分: {overallScore}%
|
||
</Badge>
|
||
<Avatar className="h-8 w-8 sm:h-10 sm:w-10 ring-2 ring-blue-200">
|
||
<AvatarImage src="/placeholder.svg?height=40&width=40" />
|
||
<AvatarFallback className="bg-gradient-to-br from-blue-500 to-purple-500 text-white">CEO</AvatarFallback>
|
||
</Avatar>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Performance Alerts */}
|
||
{underperformingKPIs.length > 0 && (
|
||
<Alert className="border-red-200 bg-gradient-to-r from-red-50 to-pink-50 shadow-lg">
|
||
<AlertTriangle className="h-4 w-4 text-red-600" />
|
||
<AlertTitle className="text-red-800 font-semibold">績效警示</AlertTitle>
|
||
<AlertDescription className="text-red-700 text-sm sm:text-base">
|
||
{underperformingKPIs.length} 個 KPI 低於 70% 目標: {underperformingKPIs.map((kpi) => kpi.name).join(", ")}
|
||
</AlertDescription>
|
||
</Alert>
|
||
)}
|
||
|
||
{/* KPI Overview Cards */}
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4 sm:gap-6">
|
||
{loading ? (
|
||
// 載入中狀態
|
||
Array.from({ length: 4 }).map((_, index) => (
|
||
<Card key={index} className="relative overflow-hidden shadow-lg border-0 bg-gradient-to-br from-gray-50 to-gray-100">
|
||
<CardHeader className="pb-2">
|
||
<div className="animate-pulse">
|
||
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
|
||
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent className="flex flex-col items-center space-y-4">
|
||
<div className="animate-pulse">
|
||
<div className="w-24 h-24 bg-gray-200 rounded-full"></div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
))
|
||
) : (
|
||
displayKpiData.map((kpi) => (
|
||
<Card
|
||
key={kpi.id}
|
||
className={`relative overflow-hidden shadow-lg hover:shadow-xl transition-all duration-300 border-0 ${
|
||
kpi.color === "emerald"
|
||
? "bg-gradient-to-br from-emerald-50 to-green-100"
|
||
: kpi.color === "blue"
|
||
? "bg-gradient-to-br from-blue-50 to-cyan-100"
|
||
: kpi.color === "purple"
|
||
? "bg-gradient-to-br from-purple-50 to-violet-100"
|
||
: "bg-gradient-to-br from-orange-50 to-amber-100"
|
||
}`}
|
||
>
|
||
<CardHeader className="pb-2">
|
||
<div className="flex items-center justify-between">
|
||
<CardTitle className="text-sm font-medium text-gray-700">{kpi.name}</CardTitle>
|
||
<div
|
||
className={`p-2 rounded-full ${
|
||
kpi.category === "financial"
|
||
? "bg-emerald-100"
|
||
: kpi.category === "team"
|
||
? "bg-blue-100"
|
||
: kpi.category === "operational"
|
||
? "bg-purple-100"
|
||
: "bg-orange-100"
|
||
}`}
|
||
>
|
||
{kpi.category === "financial" && <DollarSign className="h-4 w-4 text-emerald-600" />}
|
||
{kpi.category === "team" && <Users className="h-4 w-4 text-blue-600" />}
|
||
{kpi.category === "operational" && <Activity className="h-4 w-4 text-purple-600" />}
|
||
{kpi.category === "innovation" && <Zap className="h-4 w-4 text-orange-600" />}
|
||
</div>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent className="flex flex-col items-center space-y-4">
|
||
<CircularProgress value={kpi.current} size={100} color={kpi.color} />
|
||
<div className="text-center space-y-1">
|
||
<div className="flex items-center justify-center space-x-2">
|
||
<span className="text-xs sm:text-sm text-gray-600">目標: {kpi.target}%</span>
|
||
{kpi.current >= kpi.target ? (
|
||
<TrendingUp className="h-3 w-3 sm:h-4 sm:w-4 text-emerald-500" />
|
||
) : (
|
||
<TrendingDown className="h-3 w-3 sm:h-4 sm:w-4 text-red-500" />
|
||
)}
|
||
</div>
|
||
<Badge
|
||
variant={kpi.current >= 70 ? "default" : "destructive"}
|
||
className={`text-xs ${
|
||
kpi.current >= 70
|
||
? "bg-gradient-to-r from-green-500 to-emerald-500"
|
||
: "bg-gradient-to-r from-red-500 to-pink-500"
|
||
}`}
|
||
>
|
||
權重: {kpi.weight}%
|
||
</Badge>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
))
|
||
)}
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4 sm:gap-6">
|
||
{/* Team Performance Ranking */}
|
||
<Card className="shadow-lg bg-gradient-to-br from-white to-blue-50">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-2 text-base sm:text-lg">
|
||
<div className="p-2 bg-blue-100 rounded-full">
|
||
<Users className="h-4 w-4 sm:h-5 sm:w-5 text-blue-600" />
|
||
</div>
|
||
<span className="bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||
團隊績效排名
|
||
</span>
|
||
</CardTitle>
|
||
<CardDescription className="text-sm text-gray-600">部門績效比較</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<ChartContainer config={chartConfig} className="h-[250px] sm:h-[300px]">
|
||
<ResponsiveContainer width="100%" height="100%">
|
||
<BarChart data={teamRankingData} layout="horizontal">
|
||
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
|
||
<XAxis type="number" domain={[0, 100]} />
|
||
<YAxis dataKey="name" type="category" width={60} className="text-xs sm:text-sm" />
|
||
<ChartTooltip content={<ChartTooltipContent />} />
|
||
<Bar dataKey="performance" fill={(entry) => entry.color} radius={[0, 8, 8, 0]} />
|
||
</BarChart>
|
||
</ResponsiveContainer>
|
||
</ChartContainer>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Review Reminders */}
|
||
<Card className="shadow-lg bg-gradient-to-br from-white to-purple-50">
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center space-x-2 text-base sm:text-lg">
|
||
<div className="p-2 bg-purple-100 rounded-full">
|
||
<Calendar className="h-4 w-4 sm:h-5 sm:w-5 text-purple-600" />
|
||
</div>
|
||
<span className="bg-gradient-to-r from-purple-600 to-pink-600 bg-clip-text text-transparent">
|
||
即將到來的審查
|
||
</span>
|
||
</CardTitle>
|
||
<CardDescription className="text-sm text-gray-600">預定的績效審查和一對一面談</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-3 sm:space-y-4">
|
||
{reviewReminders.map((review) => (
|
||
<div
|
||
key={review.id}
|
||
className={`flex flex-col sm:flex-row sm:items-center sm:justify-between p-3 sm:p-4 border-l-4 rounded-lg hover:shadow-md transition-all duration-200 space-y-2 sm:space-y-0 ${
|
||
review.priority === "high"
|
||
? "border-red-400 bg-gradient-to-r from-red-50 to-pink-50"
|
||
: review.priority === "medium"
|
||
? "border-yellow-400 bg-gradient-to-r from-yellow-50 to-amber-50"
|
||
: "border-green-400 bg-gradient-to-r from-green-50 to-emerald-50"
|
||
}`}
|
||
>
|
||
<div className="flex items-center space-x-3">
|
||
<Avatar className="h-8 w-8 sm:h-10 sm:w-10 ring-2 ring-white shadow-md">
|
||
<AvatarImage src={`/placeholder.svg?height=40&width=40&text=${review.employee.charAt(0)}`} />
|
||
<AvatarFallback
|
||
className={`${
|
||
review.priority === "high"
|
||
? "bg-gradient-to-br from-red-400 to-pink-500"
|
||
: review.priority === "medium"
|
||
? "bg-gradient-to-br from-yellow-400 to-amber-500"
|
||
: "bg-gradient-to-br from-green-400 to-emerald-500"
|
||
} text-white`}
|
||
>
|
||
{review.employee.charAt(0) || "U"}
|
||
</AvatarFallback>
|
||
</Avatar>
|
||
<div>
|
||
<p className="font-medium text-gray-900 text-sm sm:text-base">{review.employee}</p>
|
||
<p className="text-xs sm:text-sm text-gray-600">{review.role}</p>
|
||
</div>
|
||
</div>
|
||
<div className="flex sm:flex-col items-start sm:items-end space-x-2 sm:space-x-0 sm:space-y-1">
|
||
<Badge
|
||
variant="outline"
|
||
className={`text-xs border-2 ${
|
||
review.priority === "high"
|
||
? "border-red-300 text-red-700 bg-red-50"
|
||
: review.priority === "medium"
|
||
? "border-yellow-300 text-yellow-700 bg-yellow-50"
|
||
: "border-green-300 text-green-700 bg-green-50"
|
||
}`}
|
||
>
|
||
{review.type}
|
||
</Badge>
|
||
<p className="text-xs sm:text-sm text-gray-600 font-medium">{review.dueDate}</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
<Button
|
||
className="w-full mt-4 bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white border-0 shadow-lg"
|
||
variant="outline"
|
||
>
|
||
查看所有審查
|
||
</Button>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* Quick Actions */}
|
||
<Card className="shadow-lg bg-gradient-to-br from-white to-indigo-50">
|
||
<CardHeader>
|
||
<CardTitle className="text-base sm:text-lg flex items-center space-x-2">
|
||
<div className="p-2 bg-indigo-100 rounded-full">
|
||
<Award className="h-5 w-5 text-indigo-600" />
|
||
</div>
|
||
<span className="bg-gradient-to-r from-indigo-600 to-blue-600 bg-clip-text text-transparent">
|
||
快速操作
|
||
</span>
|
||
</CardTitle>
|
||
<CardDescription className="text-sm text-gray-600">常用管理工具</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4">
|
||
<Button
|
||
variant="outline"
|
||
className="h-16 sm:h-20 flex flex-col space-y-1 sm:space-y-2 bg-gradient-to-br from-emerald-50 to-green-100 border-emerald-200 hover:from-emerald-100 hover:to-green-200 text-emerald-700 hover:text-emerald-800 text-xs sm:text-sm shadow-md hover:shadow-lg transition-all duration-200"
|
||
>
|
||
<Target className="h-4 w-4 sm:h-6 sm:w-6" />
|
||
<span>設定新 KPI</span>
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
className="h-16 sm:h-20 flex flex-col space-y-1 sm:space-y-2 bg-gradient-to-br from-blue-50 to-cyan-100 border-blue-200 hover:from-blue-100 hover:to-cyan-200 text-blue-700 hover:text-blue-800 text-xs sm:text-sm shadow-md hover:shadow-lg transition-all duration-200"
|
||
>
|
||
<Calendar className="h-4 w-4 sm:h-6 sm:w-6" />
|
||
<span>安排審查</span>
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
className="h-16 sm:h-20 flex flex-col space-y-1 sm:space-y-2 bg-gradient-to-br from-purple-50 to-violet-100 border-purple-200 hover:from-purple-100 hover:to-violet-200 text-purple-700 hover:text-purple-800 text-xs sm:text-sm shadow-md hover:shadow-lg transition-all duration-200"
|
||
>
|
||
<TrendingUp className="h-4 w-4 sm:h-6 sm:w-6" />
|
||
<span>更新進度</span>
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
className="h-16 sm:h-20 flex flex-col space-y-1 sm:space-y-2 bg-gradient-to-br from-orange-50 to-amber-100 border-orange-200 hover:from-orange-100 hover:to-amber-200 text-orange-700 hover:text-orange-800 text-xs sm:text-sm shadow-md hover:shadow-lg transition-all duration-200"
|
||
>
|
||
<Users className="h-4 w-4 sm:h-6 sm:w-6" />
|
||
<span>團隊分析</span>
|
||
</Button>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|