From c6bfed931f1c763df1c7c3b5b7eb068ae8e3505c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B3=E4=BD=A9=E5=BA=AD?= Date: Sat, 4 Oct 2025 22:18:50 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=80=A3=E7=B5=90=E6=B8=AC?= =?UTF-8?q?=E9=A9=97=E9=82=80=E8=AB=8B=E8=A8=BB=E5=86=8A=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/test-links/page.tsx | 489 ++++++---------------------------- app/test-link/page.tsx | 220 +++++++++++++++ 2 files changed, 304 insertions(+), 405 deletions(-) create mode 100644 app/test-link/page.tsx diff --git a/app/admin/test-links/page.tsx b/app/admin/test-links/page.tsx index e3494f1..8dce0b8 100644 --- a/app/admin/test-links/page.tsx +++ b/app/admin/test-links/page.tsx @@ -1,435 +1,114 @@ "use client" -import { useState, useEffect } from "react" import { ProtectedRoute } from "@/components/protected-route" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" -import { Badge } from "@/components/ui/badge" -import { Alert, AlertDescription } from "@/components/ui/alert" -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { ArrowLeft, Copy, - CheckCircle, - Clock, - Users, Link as LinkIcon, - Send, - RefreshCw, Eye, - Trash2, - Plus, - Loader2, } from "lucide-react" import Link from "next/link" -// 定義測驗連結類型 -interface TestLink { - id: string - name: string - type: 'logic' | 'creative' | 'combined' - url: string - createdAt: string - expiresAt?: string - isActive: boolean - totalSent: number - completedCount: number - pendingCount: number -} - -// 定義已送出連結的狀態 -interface SentLinkStatus { - id: string - linkId: string - recipientName: string - recipientEmail: string - sentAt: string - status: 'pending' | 'completed' | 'expired' - completedAt?: string - testType: 'logic' | 'creative' | 'combined' - score?: number -} - export default function TestLinksPage() { - const [currentLinks, setCurrentLinks] = useState([]) - const [sentLinks, setSentLinks] = useState([]) - const [loading, setLoading] = useState(true) - const [newLinkName, setNewLinkName] = useState("") - const [newLinkType, setNewLinkType] = useState<'logic' | 'creative' | 'combined'>('combined') - const [showCreateForm, setShowCreateForm] = useState(false) - - // 模擬數據 - useEffect(() => { - const mockCurrentLinks: TestLink[] = [ - { - id: '1', - name: '2024年度綜合能力測驗', - type: 'combined', - url: 'https://hr-assessment.com/test/combined/abc123', - createdAt: '2024-01-15T10:00:00Z', - expiresAt: '2024-12-31T23:59:59Z', - isActive: true, - totalSent: 45, - completedCount: 38, - pendingCount: 7 - }, - { - id: '2', - name: '創意能力專項測驗', - type: 'creative', - url: 'https://hr-assessment.com/test/creative/def456', - createdAt: '2024-01-20T14:30:00Z', - isActive: true, - totalSent: 23, - completedCount: 20, - pendingCount: 3 - } - ] - - const mockSentLinks: SentLinkStatus[] = [ - { - id: '1', - linkId: '1', - recipientName: '張小明', - recipientEmail: 'zhang.xiaoming@company.com', - sentAt: '2024-01-15T10:30:00Z', - status: 'completed', - completedAt: '2024-01-16T09:15:00Z', - testType: 'combined', - score: 85 - }, - { - id: '2', - linkId: '1', - recipientName: '李美華', - recipientEmail: 'li.meihua@company.com', - sentAt: '2024-01-15T11:00:00Z', - status: 'completed', - completedAt: '2024-01-17T14:20:00Z', - testType: 'combined', - score: 92 - }, - { - id: '3', - linkId: '1', - recipientName: '王大偉', - recipientEmail: 'wang.dawei@company.com', - sentAt: '2024-01-15T11:30:00Z', - status: 'pending', - testType: 'combined' - }, - { - id: '4', - linkId: '2', - recipientName: '陳小芳', - recipientEmail: 'chen.xiaofang@company.com', - sentAt: '2024-01-20T15:00:00Z', - status: 'completed', - completedAt: '2024-01-21T10:45:00Z', - testType: 'creative', - score: 78 - } - ] - - setTimeout(() => { - setCurrentLinks(mockCurrentLinks) - setSentLinks(mockSentLinks) - setLoading(false) - }, 1000) - }, []) - - const handleCopyLink = (url: string) => { + const handleCopyLink = () => { + const url = `${typeof window !== 'undefined' ? window.location.origin : ''}/test-link` navigator.clipboard.writeText(url) - // 這裡可以添加 toast 提示 - } - - const handleCreateLink = () => { - if (!newLinkName.trim()) return - - const newLink: TestLink = { - id: Date.now().toString(), - name: newLinkName, - type: newLinkType, - url: `https://hr-assessment.com/test/${newLinkType}/${Math.random().toString(36).substr(2, 9)}`, - createdAt: new Date().toISOString(), - isActive: true, - totalSent: 0, - completedCount: 0, - pendingCount: 0 - } - - setCurrentLinks([...currentLinks, newLink]) - setNewLinkName("") - setShowCreateForm(false) - } - - const getStatusBadge = (status: string) => { - switch (status) { - case 'completed': - return 已完成 - case 'pending': - return 進行中 - case 'expired': - return 已過期 - default: - return 未知 - } - } - - const getTypeName = (type: string) => { - switch (type) { - case 'logic': - return '邏輯思維' - case 'creative': - return '創意能力' - case 'combined': - return '綜合能力' - default: - return '未知' - } - } - - if (loading) { - return ( -
-
-
- -
-
-
- ) + alert('連結已複製到剪貼簿') } return ( -
- {/* Header */} -
-
-
- -
- -
-
-

測驗連結管理

-

- 管理測驗連結的發送和追蹤測試完成狀態 -

+ +
+ {/* Header */} +
+
+
+ +
+ +
+
+

測驗連結管理

+

+ 管理測驗連結的發送和追蹤測試完成狀態 +

+
-
-
+ -
-
- - - 目前測驗連結 - 已送出連結狀態 - - - {/* 目前測驗連結 */} - - - -
-
- 目前測驗連結 - - 管理當前可用的測驗連結 - -
-
-
- - {/* 新增連結表單 */} - {showCreateForm && ( - - - 新增測驗連結 - - -
- - setNewLinkName(e.target.value)} - placeholder="請輸入連結名稱" - /> -
-
- - -
-
- - -
-
-
- )} +
- {/* 連結列表 */} -
- {currentLinks.map((link) => ( - - -
-
-
-

{link.name}

- - {link.isActive ? "啟用中" : "已停用"} - - {getTypeName(link.type)} -
-
- 建立時間: {new Date(link.createdAt).toLocaleString('zh-TW')} - {link.expiresAt && ( - 到期時間: {new Date(link.expiresAt).toLocaleString('zh-TW')} - )} -
-
- - - 總發送: {link.totalSent} - - - - 已完成: {link.completedCount} - - - - 進行中: {link.pendingCount} - -
-
-
- - -
-
-
-
- ))} -
- - - + {/* 功能說明 */} +
+

功能說明:

+
    +
  • • 受測者點擊連結後會要求填寫基本資料
  • +
  • • 系統會自動創建用戶帳號(密碼:Aa123456)
  • +
  • • 填寫完成後自動跳轉到測驗選擇頁面
  • +
  • • 支援邏輯思維、創意能力、綜合測試三種類型
  • +
+
- {/* 已送出連結狀態 */} - - - - 已送出連結狀態清單 - - 追蹤已送出測驗連結的完成狀態 - - - - - - - 收件人 - 測驗類型 - 發送時間 - 狀態 - 完成時間 - 分數 - 操作 - - - - {sentLinks.map((sentLink) => ( - - -
-
{sentLink.recipientName}
-
{sentLink.recipientEmail}
-
-
- - {getTypeName(sentLink.testType)} - - - {new Date(sentLink.sentAt).toLocaleString('zh-TW')} - - - {getStatusBadge(sentLink.status)} - - - {sentLink.completedAt - ? new Date(sentLink.completedAt).toLocaleString('zh-TW') - : '-' - } - - - {sentLink.score ? `${sentLink.score}分` : '-'} - - -
- - {sentLink.status === 'pending' && ( - - )} -
-
-
- ))} -
-
-
-
-
- + {/* 預覽按鈕 */} +
+ +
+ + +
+ - + ) -} +} \ No newline at end of file diff --git a/app/test-link/page.tsx b/app/test-link/page.tsx new file mode 100644 index 0000000..0ec6608 --- /dev/null +++ b/app/test-link/page.tsx @@ -0,0 +1,220 @@ +"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 { Label } from "@/components/ui/label" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Alert, AlertDescription } from "@/components/ui/alert" +import { Loader2, User, Mail, Building, CheckCircle } from "lucide-react" +import { useRouter } from "next/navigation" + +export default function TestLinkPage() { + const router = useRouter() + const [userInfo, setUserInfo] = useState({ + name: '', + email: '', + department: '' + }) + const [isSubmitting, setIsSubmitting] = useState(false) + const [isSuccess, setIsSuccess] = useState(false) + const [error, setError] = useState('') + + const handleUserInfoChange = (field: string, value: string) => { + setUserInfo(prev => ({ + ...prev, + [field]: value + })) + setError('') // 清除錯誤訊息 + } + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + + if (!userInfo.name || !userInfo.email || !userInfo.department) { + setError('請填寫所有必填欄位') + return + } + + setIsSubmitting(true) + setError('') + + try { + // 註冊用戶 + const response = await fetch('/api/auth/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: userInfo.name, + email: userInfo.email, + department: userInfo.department, + password: 'Aa123456', // 預設密碼 + role: 'user' + }), + }) + + if (response.ok) { + // 註冊成功後自動登入 + const loginResponse = await fetch('/api/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email: userInfo.email, + password: 'Aa123456' + }), + }) + + if (loginResponse.ok) { + const loginData = await loginResponse.json() + + // 存儲 token 和用戶資料到 localStorage + if (loginData.accessToken && loginData.refreshToken) { + localStorage.setItem('accessToken', loginData.accessToken) + localStorage.setItem('refreshToken', loginData.refreshToken) + localStorage.setItem('hr_current_user', JSON.stringify(loginData.user)) + } + + setIsSuccess(true) + // 2秒後跳轉到個人專區(儀表板) + setTimeout(() => { + // 使用 window.location.href 確保頁面完全重新載入 + window.location.href = '/home' + }, 2000) + } else { + setError('註冊成功但自動登入失敗,請手動登入') + } + } else { + const errorData = await response.json() + setError(errorData.message || '註冊失敗,請稍後再試') + } + } catch (error) { + console.error('Registration error:', error) + setError('註冊失敗,請稍後再試') + } finally { + setIsSubmitting(false) + } + } + + if (isSuccess) { + return ( +
+ + +
+
+ +
+
+

註冊成功!

+

+ 正在跳轉到個人專區... +

+
+
+
+
+
+ ) + } + + return ( +
+ + + 測驗資料收集 + + 請填寫以下資料以開始測驗 + + + +
+ {error && ( + + {error} + + )} + +
+ + handleUserInfoChange('name', e.target.value)} + required + /> +
+ +
+ + handleUserInfoChange('email', e.target.value)} + required + /> +
+ +
+ + +
+ + +
+ +
+

+ 系統將自動為您創建帳號,預設密碼為:Aa123456 +

+
+
+
+
+ ) +}