Files
ai-showcase-platform/components/admin/app-management.tsx
2025-08-05 08:22:44 +08:00

992 lines
39 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Badge } from "@/components/ui/badge"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import {
Search,
Plus,
MoreHorizontal,
Edit,
Trash2,
Eye,
Star,
Heart,
TrendingUp,
Bot,
CheckCircle,
Clock,
MessageSquare,
ExternalLink,
AlertTriangle,
X,
Check,
TrendingDown,
Link,
Zap,
Brain,
Mic,
ImageIcon,
FileText,
BarChart3,
Camera,
Music,
Video,
Code,
Database,
Globe,
Smartphone,
Monitor,
Headphones,
Palette,
Calculator,
Shield,
Settings,
Lightbulb,
} from "lucide-react"
// Add available icons array after imports
const availableIcons = [
{ name: "Bot", icon: Bot, color: "from-blue-500 to-purple-500" },
{ name: "Brain", icon: Brain, color: "from-purple-500 to-pink-500" },
{ name: "Zap", icon: Zap, color: "from-yellow-500 to-orange-500" },
{ name: "Mic", icon: Mic, color: "from-green-500 to-teal-500" },
{ name: "ImageIcon", icon: ImageIcon, color: "from-pink-500 to-rose-500" },
{ name: "FileText", icon: FileText, color: "from-blue-500 to-cyan-500" },
{ name: "BarChart3", icon: BarChart3, color: "from-emerald-500 to-green-500" },
{ name: "Camera", icon: Camera, color: "from-indigo-500 to-purple-500" },
{ name: "Music", icon: Music, color: "from-violet-500 to-purple-500" },
{ name: "Video", icon: Video, color: "from-red-500 to-pink-500" },
{ name: "Code", icon: Code, color: "from-gray-500 to-slate-500" },
{ name: "Database", icon: Database, color: "from-cyan-500 to-blue-500" },
{ name: "Globe", icon: Globe, color: "from-blue-500 to-indigo-500" },
{ name: "Smartphone", icon: Smartphone, color: "from-slate-500 to-gray-500" },
{ name: "Monitor", icon: Monitor, color: "from-gray-600 to-slate-600" },
{ name: "Headphones", icon: Headphones, color: "from-purple-500 to-violet-500" },
{ name: "Palette", icon: Palette, color: "from-pink-500 to-purple-500" },
{ name: "Calculator", icon: Calculator, color: "from-orange-500 to-red-500" },
{ name: "Shield", icon: Shield, color: "from-green-500 to-emerald-500" },
{ name: "Settings", icon: Settings, color: "from-gray-500 to-zinc-500" },
{ name: "Lightbulb", icon: Lightbulb, color: "from-yellow-500 to-amber-500" },
]
// App data - empty for production
const mockApps: any[] = []
export function AppManagement() {
const [apps, setApps] = useState(mockApps)
const [searchTerm, setSearchTerm] = useState("")
const [selectedType, setSelectedType] = useState("all")
const [selectedStatus, setSelectedStatus] = useState("all")
const [selectedApp, setSelectedApp] = useState<any>(null)
const [showAppDetail, setShowAppDetail] = useState(false)
const [showAddApp, setShowAddApp] = useState(false)
const [showEditApp, setShowEditApp] = useState(false)
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
const [showApprovalDialog, setShowApprovalDialog] = useState(false)
const [approvalAction, setApprovalAction] = useState<"approve" | "reject">("approve")
const [approvalReason, setApprovalReason] = useState("")
const [newApp, setNewApp] = useState({
name: "",
type: "文字處理",
department: "HQBU",
creator: "",
description: "",
appUrl: "",
icon: "Bot",
iconColor: "from-blue-500 to-purple-500",
})
const filteredApps = apps.filter((app) => {
const matchesSearch =
app.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
app.creator.toLowerCase().includes(searchTerm.toLowerCase())
const matchesType = selectedType === "all" || app.type === selectedType
const matchesStatus = selectedStatus === "all" || app.status === selectedStatus
return matchesSearch && matchesType && matchesStatus
})
const handleViewApp = (app: any) => {
setSelectedApp(app)
setShowAppDetail(true)
}
const handleEditApp = (app: any) => {
setSelectedApp(app)
setNewApp({
name: app.name,
type: app.type,
department: app.department,
creator: app.creator,
description: app.description,
appUrl: app.appUrl,
icon: app.icon || "Bot",
iconColor: app.iconColor || "from-blue-500 to-purple-500",
})
setShowEditApp(true)
}
const handleDeleteApp = (app: any) => {
setSelectedApp(app)
setShowDeleteConfirm(true)
}
const confirmDeleteApp = () => {
if (selectedApp) {
setApps(apps.filter((app) => app.id !== selectedApp.id))
setShowDeleteConfirm(false)
setSelectedApp(null)
}
}
const handleToggleAppStatus = (appId: string) => {
setApps(
apps.map((app) =>
app.id === appId
? {
...app,
status: app.status === "published" ? "draft" : "published",
}
: app,
),
)
}
const handleApprovalAction = (app: any, action: "approve" | "reject") => {
setSelectedApp(app)
setApprovalAction(action)
setApprovalReason("")
setShowApprovalDialog(true)
}
const confirmApproval = () => {
if (selectedApp) {
setApps(
apps.map((app) =>
app.id === selectedApp.id
? {
...app,
status: approvalAction === "approve" ? "published" : "rejected",
}
: app,
),
)
setShowApprovalDialog(false)
setSelectedApp(null)
setApprovalReason("")
}
}
const handleAddApp = () => {
const app = {
id: Date.now().toString(),
...newApp,
status: "pending",
createdAt: new Date().toISOString().split("T")[0],
views: 0,
likes: 0,
rating: 0,
reviews: 0,
}
setApps([...apps, app])
setNewApp({
name: "",
type: "文字處理",
department: "HQBU",
creator: "",
description: "",
appUrl: "",
icon: "Bot",
iconColor: "from-blue-500 to-purple-500",
})
setShowAddApp(false)
}
const handleUpdateApp = () => {
if (selectedApp) {
setApps(
apps.map((app) =>
app.id === selectedApp.id
? {
...app,
...newApp,
}
: app,
),
)
setShowEditApp(false)
setSelectedApp(null)
}
}
const getStatusColor = (status: string) => {
switch (status) {
case "published":
return "bg-green-100 text-green-800 border-green-200"
case "pending":
return "bg-yellow-100 text-yellow-800 border-yellow-200"
case "draft":
return "bg-gray-100 text-gray-800 border-gray-200"
case "rejected":
return "bg-red-100 text-red-800 border-red-200"
default:
return "bg-gray-100 text-gray-800 border-gray-200"
}
}
const getTypeColor = (type: string) => {
const colors = {
: "bg-blue-100 text-blue-800 border-blue-200",
: "bg-purple-100 text-purple-800 border-purple-200",
: "bg-green-100 text-green-800 border-green-200",
: "bg-orange-100 text-orange-800 border-orange-200",
}
return colors[type as keyof typeof colors] || "bg-gray-100 text-gray-800 border-gray-200"
}
const getStatusText = (status: string) => {
switch (status) {
case "published":
return "已發布"
case "pending":
return "待審核"
case "draft":
return "草稿"
case "rejected":
return "已拒絕"
default:
return status
}
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-900"></h1>
<p className="text-gray-600"> AI </p>
</div>
<Button
onClick={() => setShowAddApp(true)}
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700"
>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold">{apps.length}</p>
</div>
<Bot className="w-8 h-8 text-blue-600" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold">{apps.filter((a) => a.status === "published").length}</p>
</div>
<CheckCircle className="w-8 h-8 text-green-600" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold">{apps.filter((a) => a.status === "pending").length}</p>
</div>
<Clock className="w-8 h-8 text-yellow-600" />
</div>
</CardContent>
</Card>
</div>
{/* Filters */}
<Card>
<CardContent className="p-4">
<div className="flex flex-col lg:flex-row gap-4 items-center">
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
placeholder="搜尋應用名稱或創建者..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
<div className="flex gap-3">
<Select value={selectedType} onValueChange={setSelectedType}>
<SelectTrigger className="w-32">
<SelectValue placeholder="類型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="文字處理"></SelectItem>
<SelectItem value="圖像生成"></SelectItem>
<SelectItem value="語音辨識"></SelectItem>
<SelectItem value="推薦系統"></SelectItem>
</SelectContent>
</Select>
<Select value={selectedStatus} onValueChange={setSelectedStatus}>
<SelectTrigger className="w-32">
<SelectValue placeholder="狀態" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="published"></SelectItem>
<SelectItem value="pending"></SelectItem>
<SelectItem value="draft">稿</SelectItem>
<SelectItem value="rejected"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
{/* Apps Table */}
<Card>
<CardHeader>
<CardTitle> ({filteredApps.length})</CardTitle>
<CardDescription> AI </CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredApps.map((app) => (
<TableRow key={app.id}>
<TableCell>
<div className="flex items-center space-x-3">
<div
className={`w-8 h-8 bg-gradient-to-r ${app.iconColor} rounded-lg flex items-center justify-center`}
>
{(() => {
const IconComponent = availableIcons.find((icon) => icon.name === app.icon)?.icon || Bot
return <IconComponent className="w-4 h-4 text-white" />
})()}
</div>
<div>
<div className="flex items-center space-x-2">
<p className="font-medium">{app.name}</p>
{app.appUrl && (
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0"
onClick={() => window.open(app.appUrl, "_blank")}
title="開啟應用"
>
<ExternalLink className="w-3 h-3" />
</Button>
)}
</div>
</div>
</div>
</TableCell>
<TableCell>
<Badge variant="outline" className={getTypeColor(app.type)}>
{app.type}
</Badge>
</TableCell>
<TableCell>
<div>
<p className="font-medium">{app.creator}</p>
<p className="text-sm text-gray-500">{app.department}</p>
</div>
</TableCell>
<TableCell>
<Badge variant="outline" className={getStatusColor(app.status)}>
{getStatusText(app.status)}
</Badge>
</TableCell>
<TableCell>
<div className="text-sm">
<div className="flex items-center space-x-1">
<Eye className="w-3 h-3" />
<span>{app.views}</span>
</div>
<div className="flex items-center space-x-1">
<Heart className="w-3 h-3" />
<span>{app.likes}</span>
</div>
</div>
</TableCell>
<TableCell>
<div className="flex items-center space-x-1">
<Star className="w-4 h-4 text-yellow-500" />
<span className="font-medium">{app.rating}</span>
<span className="text-sm text-gray-500">({app.reviews})</span>
</div>
</TableCell>
<TableCell className="text-sm text-gray-600">{app.createdAt}</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm">
<MoreHorizontal className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => handleViewApp(app)}>
<Eye className="w-4 h-4 mr-2" />
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleEditApp(app)}>
<Edit className="w-4 h-4 mr-2" />
</DropdownMenuItem>
{app.appUrl && (
<DropdownMenuItem onClick={() => window.open(app.appUrl, "_blank")}>
<ExternalLink className="w-4 h-4 mr-2" />
</DropdownMenuItem>
)}
{app.status === "pending" && (
<>
<DropdownMenuItem onClick={() => handleApprovalAction(app, "approve")}>
<Check className="w-4 h-4 mr-2" />
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleApprovalAction(app, "reject")}>
<X className="w-4 h-4 mr-2" />
</DropdownMenuItem>
</>
)}
{app.status !== "pending" && (
<DropdownMenuItem onClick={() => handleToggleAppStatus(app.id)}>
{app.status === "published" ? (
<>
<TrendingDown className="w-4 h-4 mr-2" />
</>
) : (
<>
<TrendingUp className="w-4 h-4 mr-2" />
</>
)}
</DropdownMenuItem>
)}
<DropdownMenuItem className="text-red-600" onClick={() => handleDeleteApp(app)}>
<Trash2 className="w-4 h-4 mr-2" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
{/* Add App Dialog */}
<Dialog open={showAddApp} onOpenChange={setShowAddApp}>
<DialogContent className="max-w-4xl max-h-[85vh] overflow-y-auto">
<DialogHeader>
<DialogTitle> AI </DialogTitle>
<DialogDescription> AI </DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="name"> *</Label>
<Input
id="name"
value={newApp.name}
onChange={(e) => setNewApp({ ...newApp, name: e.target.value })}
placeholder="輸入應用名稱"
/>
</div>
<div className="space-y-2">
<Label htmlFor="creator"> *</Label>
<Input
id="creator"
value={newApp.creator}
onChange={(e) => setNewApp({ ...newApp, creator: e.target.value })}
placeholder="輸入創建者姓名"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="type"></Label>
<Select value={newApp.type} onValueChange={(value) => setNewApp({ ...newApp, type: value })}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="文字處理"></SelectItem>
<SelectItem value="圖像生成"></SelectItem>
<SelectItem value="語音辨識"></SelectItem>
<SelectItem value="推薦系統"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="department"></Label>
<Select
value={newApp.department}
onValueChange={(value) => setNewApp({ ...newApp, department: value })}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="HQBU">HQBU</SelectItem>
<SelectItem value="ITBU">ITBU</SelectItem>
<SelectItem value="MBU1">MBU1</SelectItem>
<SelectItem value="SBU">SBU</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="icon"></Label>
<div className="grid grid-cols-7 gap-2 p-4 border rounded-lg max-h-60 overflow-y-auto bg-gray-50">
{availableIcons.map((iconOption) => {
const IconComponent = iconOption.icon
return (
<button
key={iconOption.name}
type="button"
onClick={() => {
setNewApp({
...newApp,
icon: iconOption.name,
iconColor: iconOption.color,
})
}}
className={`w-12 h-12 rounded-lg flex items-center justify-center transition-all hover:scale-105 ${
newApp.icon === iconOption.name
? `bg-gradient-to-r ${iconOption.color} shadow-lg ring-2 ring-blue-500`
: `bg-gradient-to-r ${iconOption.color} opacity-70 hover:opacity-100`
}`}
title={iconOption.name}
>
<IconComponent className="w-6 h-6 text-white" />
</button>
)
})}
</div>
<p className="text-xs text-gray-500"></p>
</div>
<div className="space-y-2">
<Label htmlFor="appUrl"></Label>
<div className="relative">
<Link className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="appUrl"
value={newApp.appUrl}
onChange={(e) => setNewApp({ ...newApp, appUrl: e.target.value })}
placeholder="https://your-app.example.com"
className="pl-10"
/>
</div>
<p className="text-xs text-gray-500"></p>
</div>
<div className="space-y-2">
<Label htmlFor="description"> *</Label>
<Textarea
id="description"
value={newApp.description}
onChange={(e) => setNewApp({ ...newApp, description: e.target.value })}
placeholder="描述應用的功能和特色"
rows={3}
/>
</div>
<div className="flex justify-end space-x-3">
<Button variant="outline" onClick={() => setShowAddApp(false)}>
</Button>
<Button onClick={handleAddApp} disabled={!newApp.name || !newApp.creator || !newApp.description}>
</Button>
</div>
</div>
</DialogContent>
</Dialog>
{/* Edit App Dialog */}
<Dialog open={showEditApp} onOpenChange={setShowEditApp}>
<DialogContent className="max-w-4xl max-h-[85vh] overflow-y-auto">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="edit-name"> *</Label>
<Input
id="edit-name"
value={newApp.name}
onChange={(e) => setNewApp({ ...newApp, name: e.target.value })}
placeholder="輸入應用名稱"
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-creator"> *</Label>
<Input
id="edit-creator"
value={newApp.creator}
onChange={(e) => setNewApp({ ...newApp, creator: e.target.value })}
placeholder="輸入創建者姓名"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="edit-type"></Label>
<Select value={newApp.type} onValueChange={(value) => setNewApp({ ...newApp, type: value })}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="文字處理"></SelectItem>
<SelectItem value="圖像生成"></SelectItem>
<SelectItem value="語音辨識"></SelectItem>
<SelectItem value="推薦系統"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="edit-department"></Label>
<Select
value={newApp.department}
onValueChange={(value) => setNewApp({ ...newApp, department: value })}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="HQBU">HQBU</SelectItem>
<SelectItem value="ITBU">ITBU</SelectItem>
<SelectItem value="MBU1">MBU1</SelectItem>
<SelectItem value="SBU">SBU</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="icon"></Label>
<div className="grid grid-cols-7 gap-2 p-4 border rounded-lg max-h-60 overflow-y-auto bg-gray-50">
{availableIcons.map((iconOption) => {
const IconComponent = iconOption.icon
return (
<button
key={iconOption.name}
type="button"
onClick={() => {
setNewApp({
...newApp,
icon: iconOption.name,
iconColor: iconOption.color,
})
}}
className={`w-12 h-12 rounded-lg flex items-center justify-center transition-all hover:scale-105 ${
newApp.icon === iconOption.name
? `bg-gradient-to-r ${iconOption.color} shadow-lg ring-2 ring-blue-500`
: `bg-gradient-to-r ${iconOption.color} opacity-70 hover:opacity-100`
}`}
title={iconOption.name}
>
<IconComponent className="w-6 h-6 text-white" />
</button>
)
})}
</div>
</div>
<div className="space-y-2">
<Label htmlFor="edit-appUrl"></Label>
<div className="relative">
<Link className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="edit-appUrl"
value={newApp.appUrl}
onChange={(e) => setNewApp({ ...newApp, appUrl: e.target.value })}
placeholder="https://your-app.example.com"
className="pl-10"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="edit-description"> *</Label>
<Textarea
id="edit-description"
value={newApp.description}
onChange={(e) => setNewApp({ ...newApp, description: e.target.value })}
placeholder="描述應用的功能和特色"
rows={3}
/>
</div>
<div className="flex justify-end space-x-3">
<Button variant="outline" onClick={() => setShowEditApp(false)}>
</Button>
<Button onClick={handleUpdateApp} disabled={!newApp.name || !newApp.creator || !newApp.description}>
</Button>
</div>
</div>
</DialogContent>
</Dialog>
{/* Delete Confirmation Dialog */}
<Dialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
<DialogContent>
<DialogHeader>
<DialogTitle className="flex items-center space-x-2">
<AlertTriangle className="w-5 h-5 text-red-500" />
<span></span>
</DialogTitle>
<DialogDescription>{selectedApp?.name}</DialogDescription>
</DialogHeader>
<div className="flex justify-end space-x-3">
<Button variant="outline" onClick={() => setShowDeleteConfirm(false)}>
</Button>
<Button variant="destructive" onClick={confirmDeleteApp}>
</Button>
</div>
</DialogContent>
</Dialog>
{/* Approval Dialog */}
<Dialog open={showApprovalDialog} onOpenChange={setShowApprovalDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle className="flex items-center space-x-2">
{approvalAction === "approve" ? (
<CheckCircle className="w-5 h-5 text-green-500" />
) : (
<X className="w-5 h-5 text-red-500" />
)}
<span>{approvalAction === "approve" ? "批准應用" : "拒絕應用"}</span>
</DialogTitle>
<DialogDescription>
{approvalAction === "approve"
? `確認批准應用「${selectedApp?.name}」並發布到平台?`
: `確認拒絕應用「${selectedApp?.name}」的發布申請?`}
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="approval-reason">
{approvalAction === "approve" ? "批准備註(可選)" : "拒絕原因 *"}
</Label>
<Textarea
id="approval-reason"
value={approvalReason}
onChange={(e) => setApprovalReason(e.target.value)}
placeholder={approvalAction === "approve" ? "輸入批准備註..." : "請說明拒絕原因,以便開發者了解並改進"}
rows={3}
/>
</div>
<div className="flex justify-end space-x-3">
<Button variant="outline" onClick={() => setShowApprovalDialog(false)}>
</Button>
<Button
onClick={confirmApproval}
variant={approvalAction === "approve" ? "default" : "destructive"}
disabled={approvalAction === "reject" && !approvalReason.trim()}
>
{approvalAction === "approve" ? "確認批准" : "確認拒絕"}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
{/* App Detail Dialog */}
<Dialog open={showAppDetail} onOpenChange={setShowAppDetail}>
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
{selectedApp && (
<Tabs defaultValue="info" className="w-full">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="info"></TabsTrigger>
<TabsTrigger value="stats"></TabsTrigger>
<TabsTrigger value="reviews"></TabsTrigger>
</TabsList>
<TabsContent value="info" className="space-y-4">
<div className="flex items-start space-x-4">
<div
className={`w-16 h-16 bg-gradient-to-r ${selectedApp.iconColor} rounded-xl flex items-center justify-center`}
>
{(() => {
const IconComponent = availableIcons.find((icon) => icon.name === selectedApp.icon)?.icon || Bot
return <IconComponent className="w-8 h-8 text-white" />
})()}
</div>
<div className="flex-1">
<div className="flex items-center space-x-2 mb-2">
<h3 className="text-xl font-semibold">{selectedApp.name}</h3>
{selectedApp.appUrl && (
<Button variant="outline" size="sm" onClick={() => window.open(selectedApp.appUrl, "_blank")}>
<ExternalLink className="w-4 h-4 mr-2" />
</Button>
)}
</div>
<p className="text-gray-600 mb-2">{selectedApp.description}</p>
<div className="flex flex-wrap gap-2">
<Badge variant="outline" className={getTypeColor(selectedApp.type)}>
{selectedApp.type}
</Badge>
<Badge variant="outline" className="bg-gray-100 text-gray-700">
{selectedApp.department}
</Badge>
<Badge variant="outline" className={getStatusColor(selectedApp.status)}>
{getStatusText(selectedApp.status)}
</Badge>
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-500"></p>
<p className="font-medium">{selectedApp.creator}</p>
</div>
<div>
<p className="text-sm text-gray-500"></p>
<p className="font-medium">{selectedApp.createdAt}</p>
</div>
<div>
<p className="text-sm text-gray-500">ID</p>
<p className="font-medium">{selectedApp.id}</p>
</div>
<div>
<p className="text-sm text-gray-500"></p>
<p className="font-medium">{selectedApp.department}</p>
</div>
</div>
{selectedApp.appUrl && (
<div>
<p className="text-sm text-gray-500"></p>
<div className="flex items-center space-x-2">
<p className="font-medium text-blue-600">{selectedApp.appUrl}</p>
<Button
variant="ghost"
size="sm"
onClick={() => navigator.clipboard.writeText(selectedApp.appUrl)}
>
</Button>
</div>
</div>
)}
</TabsContent>
<TabsContent value="stats" className="space-y-4">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<Card>
<CardContent className="p-4">
<div className="text-center">
<p className="text-2xl font-bold text-blue-600">{selectedApp.views}</p>
<p className="text-sm text-gray-600"></p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-center">
<p className="text-2xl font-bold text-red-600">{selectedApp.likes}</p>
<p className="text-sm text-gray-600"></p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-center">
<p className="text-2xl font-bold text-yellow-600">{selectedApp.rating}</p>
<p className="text-sm text-gray-600"></p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-center">
<p className="text-2xl font-bold text-green-600">{selectedApp.reviews}</p>
<p className="text-sm text-gray-600"></p>
</div>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="reviews" className="space-y-4">
<div className="text-center py-8">
<MessageSquare className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-gray-600 mb-2"></h3>
<p className="text-gray-500"></p>
</div>
</TabsContent>
</Tabs>
)}
</DialogContent>
</Dialog>
</div>
)
}