Files
re-AstarCupWeb/app/debug/components/TournamentConfigCard.tsx
2026-02-04 17:33:28 +08:00

386 lines
15 KiB
TypeScript

"use client"
import { useState, useEffect, use } from 'react';
import { Season, Category } from '@/app/generated/prisma/enums';
import { useSeasonOptions, useCategoryOptions } from '@/app/lib/enum-labels';
interface TournamentConfigData {
id: number;
tournament_name: string;
max_pp_for_registration: number;
min_pp_for_registration: number;
current_seasonal: Season;
current_category: Category;
canRegister: boolean;
createdAt: Date;
updatedAt: Date;
}
interface TournamentConfigCardProps {
initialData?: TournamentConfigData;
onUpdate?: (data: TournamentConfigData) => void;
onError?: (error: Error) => void;
}
export default function TournamentConfigCard({
initialData,
onUpdate,
onError
}: TournamentConfigCardProps) {
// 状态管理
const [config, setConfig] = useState<TournamentConfigData | null>(initialData || null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
// 表单状态
const [formData, setFormData] = useState({
tournament_name: '',
max_pp_for_registration: 0,
min_pp_for_registration: 0,
current_seasonal: 'S1' as Season,
current_category: 'QUA' as Category,
canRegister: false,
});
// 枚举选项
const seasonOptions = useSeasonOptions();
const categoryOptions = useCategoryOptions();
// 初始化函数
const initializeForm = (data: TournamentConfigData) => {
setFormData({
tournament_name: data.tournament_name,
max_pp_for_registration: data.max_pp_for_registration,
min_pp_for_registration: data.min_pp_for_registration,
current_seasonal: data.current_seasonal,
current_category: data.current_category,
canRegister: data.canRegister,
});
};
const initConfig = async () => {
try {
const response = await fetch('/api/admin/init-database', {
method: 'GET',
});
const data = await response.json();
setConfig(data);
initializeForm(data);
} catch (error) {
console.error('初始化比赛配置失败:', error);
}
}
// 加载配置
const loadConfig = async () => {
setLoading(true);
setError(null);
try {
// 这里需要调用 API 路由
const response = await fetch('/api/config',{ method:'GET',});
if (!response.ok) {
throw new Error(`加载失败: ${response.status}`);
}
const data = await response.json();
setConfig(data);
initializeForm(data);
setSuccess('配置加载成功');
if (onUpdate) {
onUpdate(data);
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : '未知错误';
setError(`加载配置失败: ${errorMessage}`);
if (onError) {
onError(err instanceof Error ? err : new Error(errorMessage));
}
} finally {
setLoading(false);
}
};
// 保存配置
const saveConfig = async () => {
setLoading(true);
setError(null);
setSuccess(null);
try {
const response = await fetch('/api/admin/set-config', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
if (!response.ok) {
throw new Error(`保存失败: ${response.status}`);
}
const data = await response.json();
setConfig(data);
setSuccess('配置保存成功');
if (onUpdate) {
onUpdate(data);
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : '未知错误';
setError(`保存配置失败: ${errorMessage}`);
if (onError) {
onError(err instanceof Error ? err : new Error(errorMessage));
}
} finally {
setLoading(false);
}
};
// 重置为默认值
const resetToDefaults = () => {
setFormData({
tournament_name: 'AstarCup',
max_pp_for_registration: 0,
min_pp_for_registration: 0,
current_seasonal: 'S1',
current_category: 'QUA',
canRegister: false,
});
setSuccess('已重置为默认值');
};
// 表单变化处理
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value, type } = e.target;
if (type === 'checkbox') {
const checked = (e.target as HTMLInputElement).checked;
setFormData(prev => ({ ...prev, [name]: checked }));
} else if (type === 'number') {
setFormData(prev => ({ ...prev, [name]: parseFloat(value) || 0 }));
} else {
setFormData(prev => ({ ...prev, [name]: value }));
}
};
// 组件挂载时加载数据
useEffect(() => {
if (!initialData) {
loadConfig();
} else {
initializeForm(initialData);
}
}, [initialData]);
// 渲染组件
return (
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold text-gray-800"></h2>
<div className="flex space-x-2">
<button
onClick={loadConfig}
disabled={loading}
className="px-3 py-1 bg-blue-100 text-blue-700 rounded hover:bg-blue-200 disabled:opacity-50"
>
</button>
<button
onClick={initConfig}
disabled={loading}
className="px-3 py-1 bg-blue-100 text-blue-700 rounded hover:bg-blue-200 disabled:opacity-50"
>
</button>
<button
onClick={resetToDefaults}
disabled={loading}
className="px-3 py-1 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 disabled:opacity-50"
>
</button>
</div>
</div>
{/* 状态提示 */}
{error && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 text-red-700 rounded">
{error}
</div>
)}
{success && (
<div className="mb-4 p-3 bg-green-50 border border-green-200 text-green-700 rounded">
{success}
</div>
)}
{/* 配置信息 */}
{config && (
<div className="mb-4 p-3 bg-gray-50 rounded text-sm">
<div className="grid grid-cols-2 gap-2">
<div>ID: <span className="font-mono">{config.id}</span></div>
<div>: <span className="font-mono">{new Date(config.createdAt).toLocaleString()}</span></div>
<div>: <span className="font-mono">{new Date(config.updatedAt).toLocaleString()}</span></div>
</div>
</div>
)}
{/* 表单 */}
<div className="space-y-4">
{/* 比赛名称 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<input
type="text"
name="tournament_name"
value={formData.tournament_name}
onChange={handleInputChange}
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="输入比赛名称"
/>
</div>
{/* PP 限制 */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
PP限制
</label>
<input
type="number"
name="min_pp_for_registration"
value={formData.min_pp_for_registration}
onChange={handleInputChange}
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
step="0.1"
min="0"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
PP限制
</label>
<input
type="number"
name="max_pp_for_registration"
value={formData.max_pp_for_registration}
onChange={handleInputChange}
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
step="0.1"
min="0"
/>
</div>
</div>
{/* 赛季和分类 */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<select
name="current_seasonal"
value={formData.current_seasonal}
onChange={handleInputChange}
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{seasonOptions.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<select
name="current_category"
value={formData.current_category}
onChange={handleInputChange}
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{categoryOptions.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
</div>
{/* 注册开关 */}
<div className="flex items-center">
<input
type="checkbox"
id="canRegister"
name="canRegister"
checked={formData.canRegister}
onChange={handleInputChange}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="canRegister" className="ml-2 block text-sm text-gray-700">
</label>
<span className="ml-2 text-xs text-gray-500">
{formData.canRegister ? '✓ 注册已开放' : '✗ 注册已关闭'}
</span>
</div>
{/* 操作按钮 */}
<div className="flex justify-end space-x-3 pt-4 border-t">
<button
onClick={loadConfig}
disabled={loading}
className="px-4 py-2 border border-gray-300 text-gray-700 rounded hover:bg-gray-50 disabled:opacity-50"
>
</button>
<button
onClick={saveConfig}
disabled={loading}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 flex items-center"
>
{loading ? (
<>
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
...
</>
) : (
'保存配置'
)}
</button>
</div>
</div>
{/* 调试信息(开发环境显示) */}
{process.env.NODE_ENV === 'development' && (
<div className="mt-6 pt-4 border-t">
<h3 className="text-sm font-medium text-gray-700 mb-2"></h3>
<pre className="text-xs bg-gray-50 p-3 rounded overflow-auto">
{JSON.stringify({
formData,
config,
loading,
error,
success,
}, null, 2)}
</pre>
</div>
)}
</div>
);
}