Files
Martinhal_Futebol_2026/nextjs_space/app/admin/page.tsx
T
2026-09-13 20:20:06 +01:00

486 lines
18 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import {
Settings,
Key,
BarChart3,
Globe,
ArrowLeft,
Save,
RefreshCw,
Eye,
EyeOff,
Check,
AlertCircle,
} from 'lucide-react';
import { ApiStatsChart } from './_components/api-stats-chart';
interface SettingsData {
id: string;
apiKey: string | null;
selectedLeague: string;
leagueId: number;
season: number;
siteName: string;
primaryColor: string;
secondaryColor: string;
showLiveMatches: boolean;
showStandings: boolean;
showTopScorers: boolean;
showRecentResults: boolean;
showUpcoming: boolean;
refreshInterval: number;
}
interface ApiStats {
totalRequests: number;
requestsToday: number;
requestsByEndpoint: { endpoint: string; count: number }[];
chartData: { time: string; count: number }[];
avgResponseTime: number;
successRate: number;
}
export default function AdminPage() {
const [settings, setSettings] = useState<SettingsData | null>(null);
const [stats, setStats] = useState<ApiStats | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [showApiKey, setShowApiKey] = useState(false);
const [saveSuccess, setSaveSuccess] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
setLoading(true);
const [settingsRes, statsRes] = await Promise.all([
fetch('/api/admin/settings'),
fetch('/api/admin/stats'),
]);
if (settingsRes.ok) {
const settingsData = await settingsRes.json();
setSettings(settingsData);
}
if (statsRes.ok) {
const statsData = await statsRes.json();
setStats(statsData);
}
} catch (err) {
setError('Failed to load data');
console.error(err);
} finally {
setLoading(false);
}
};
const saveSettings = async () => {
if (!settings) return;
try {
setSaving(true);
setError(null);
const res = await fetch('/api/admin/settings', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(settings),
});
if (res.ok) {
const updatedSettings = await res.json();
setSettings(updatedSettings);
setSaveSuccess(true);
setTimeout(() => setSaveSuccess(false), 3000);
} else {
setError('Failed to save settings');
}
} catch (err) {
setError('Failed to save settings');
console.error(err);
} finally {
setSaving(false);
}
};
const updateSetting = <K extends keyof SettingsData>(
key: K,
value: SettingsData[K]
) => {
if (settings) {
setSettings({ ...settings, [key]: value });
}
};
if (loading) {
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-4 border-green-500 border-t-transparent"></div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800">
{/* Header */}
<header className="bg-gray-900/80 backdrop-blur-md border-b border-gray-700 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<Link
href="/"
className="flex items-center gap-2 text-gray-400 hover:text-white transition-colors"
>
<ArrowLeft className="h-5 w-5" />
<span>Back to Site</span>
</Link>
<div className="h-6 w-px bg-gray-700" />
<h1 className="text-xl font-bold text-white flex items-center gap-2">
<Settings className="h-6 w-6 text-green-500" />
Backoffice
</h1>
</div>
<div className="flex items-center gap-3">
<button
onClick={fetchData}
className="flex items-center gap-2 px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition-colors"
>
<RefreshCw className="h-4 w-4" />
Refresh
</button>
<button
onClick={saveSettings}
disabled={saving}
className="flex items-center gap-2 px-4 py-2 bg-green-600 hover:bg-green-500 text-white rounded-lg transition-colors disabled:opacity-50"
>
{saving ? (
<RefreshCw className="h-4 w-4 animate-spin" />
) : saveSuccess ? (
<Check className="h-4 w-4" />
) : (
<Save className="h-4 w-4" />
)}
{saveSuccess ? 'Saved!' : 'Save Changes'}
</button>
</div>
</div>
</header>
{error && (
<div className="max-w-7xl mx-auto px-4 pt-4">
<div className="bg-red-500/20 border border-red-500 text-red-400 px-4 py-3 rounded-lg flex items-center gap-2">
<AlertCircle className="h-5 w-5" />
{error}
</div>
</div>
)}
<main className="max-w-7xl mx-auto px-4 py-8">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Layout Settings */}
<section className="bg-gray-800/50 backdrop-blur-sm rounded-xl border border-gray-700 p-6">
<h2 className="text-lg font-semibold text-white flex items-center gap-2 mb-6">
<Settings className="h-5 w-5 text-green-500" />
Layout Settings
</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Site Name
</label>
<input
type="text"
value={settings?.siteName || ''}
onChange={(e) => updateSetting('siteName', e.target.value)}
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:ring-2 focus:ring-green-500 focus:border-transparent"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Primary Color
</label>
<div className="flex items-center gap-2">
<input
type="color"
value={settings?.primaryColor || '#E42518'}
onChange={(e) =>
updateSetting('primaryColor', e.target.value)
}
className="w-12 h-10 rounded cursor-pointer"
/>
<input
type="text"
value={settings?.primaryColor || ''}
onChange={(e) =>
updateSetting('primaryColor', e.target.value)
}
className="flex-1 px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white text-sm"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Secondary Color
</label>
<div className="flex items-center gap-2">
<input
type="color"
value={settings?.secondaryColor || '#006600'}
onChange={(e) =>
updateSetting('secondaryColor', e.target.value)
}
className="w-12 h-10 rounded cursor-pointer"
/>
<input
type="text"
value={settings?.secondaryColor || ''}
onChange={(e) =>
updateSetting('secondaryColor', e.target.value)
}
className="flex-1 px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white text-sm"
/>
</div>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Refresh Interval (seconds)
</label>
<input
type="number"
min="60"
max="3600"
value={settings?.refreshInterval || 300}
onChange={(e) =>
updateSetting('refreshInterval', parseInt(e.target.value))
}
className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:ring-2 focus:ring-green-500 focus:border-transparent"
/>
</div>
<div className="space-y-3 pt-2">
<label className="block text-sm font-medium text-gray-300">
Visible Sections
</label>
{[
{ key: 'showLiveMatches', label: 'Live Matches' },
{ key: 'showStandings', label: 'Standings Table' },
{ key: 'showTopScorers', label: 'Top Scorers' },
{ key: 'showRecentResults', label: 'Recent Results' },
{ key: 'showUpcoming', label: 'Upcoming Fixtures' },
].map((item) => (
<label
key={item.key}
className="flex items-center gap-3 cursor-pointer"
>
<input
type="checkbox"
checked={
settings?.[item.key as keyof SettingsData] as boolean
}
onChange={(e) =>
updateSetting(
item.key as keyof SettingsData,
e.target.checked as never
)
}
className="w-5 h-5 rounded bg-gray-700 border-gray-600 text-green-500 focus:ring-green-500"
/>
<span className="text-gray-300">{item.label}</span>
</label>
))}
</div>
</div>
</section>
{/* API Key Management */}
<section className="bg-gray-800/50 backdrop-blur-sm rounded-xl border border-gray-700 p-6">
<h2 className="text-lg font-semibold text-white flex items-center gap-2 mb-6">
<Key className="h-5 w-5 text-yellow-500" />
API Key Management
</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
API-Football API Key
</label>
<div className="relative">
<input
type={showApiKey ? 'text' : 'password'}
value={settings?.apiKey || ''}
onChange={(e) => updateSetting('apiKey', e.target.value)}
placeholder="Enter your API key"
className="w-full px-4 py-2 pr-12 bg-gray-700 border border-gray-600 rounded-lg text-white focus:ring-2 focus:ring-yellow-500 focus:border-transparent font-mono"
/>
<button
type="button"
onClick={() => setShowApiKey(!showApiKey)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white"
>
{showApiKey ? (
<EyeOff className="h-5 w-5" />
) : (
<Eye className="h-5 w-5" />
)}
</button>
</div>
<p className="text-sm text-gray-500 mt-2">
Get your API key from{' '}
<a
href="https://www.api-football.com/"
target="_blank"
rel="noopener noreferrer"
className="text-green-500 hover:underline"
>
api-football.com
</a>
</p>
</div>
<div className="bg-gray-700/50 rounded-lg p-4">
<h3 className="text-sm font-medium text-gray-300 mb-2">
API Status
</h3>
<div className="flex items-center gap-2">
{settings?.apiKey ? (
<>
<div className="w-3 h-3 rounded-full bg-green-500"></div>
<span className="text-green-400">API Key Configured</span>
</>
) : (
<>
<div className="w-3 h-3 rounded-full bg-red-500"></div>
<span className="text-red-400">No API Key Set</span>
</>
)}
</div>
</div>
</div>
</section>
{/* League Switcher */}
<section className="bg-gray-800/50 backdrop-blur-sm rounded-xl border border-gray-700 p-6">
<h2 className="text-lg font-semibold text-white flex items-center gap-2 mb-6">
<Globe className="h-5 w-5 text-blue-500" />
League Selection
</h2>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<button
onClick={() => updateSetting('selectedLeague', 'primeira_liga')}
className={`p-4 rounded-xl border-2 transition-all ${
settings?.selectedLeague === 'primeira_liga'
? 'border-green-500 bg-green-500/20'
: 'border-gray-600 bg-gray-700/50 hover:border-gray-500'
}`}
>
<div className="text-2xl mb-2">🇵🇹</div>
<div className="font-semibold text-white">Primeira Liga</div>
<div className="text-sm text-gray-400">Portuguese Championship</div>
</button>
<button
onClick={() => updateSetting('selectedLeague', 'euro_2026')}
className={`p-4 rounded-xl border-2 transition-all ${
settings?.selectedLeague === 'euro_2026'
? 'border-blue-500 bg-blue-500/20'
: 'border-gray-600 bg-gray-700/50 hover:border-gray-500'
}`}
>
<div className="text-2xl mb-2">🇪🇺</div>
<div className="font-semibold text-white">UEFA Euro</div>
<div className="text-sm text-gray-400">European Championship</div>
</button>
</div>
<div className="bg-gray-700/50 rounded-lg p-4">
<h3 className="text-sm font-medium text-gray-300 mb-2">
Current Configuration
</h3>
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="text-gray-400">League ID:</div>
<div className="text-white font-mono">{settings?.leagueId}</div>
<div className="text-gray-400">Season:</div>
<div className="text-white font-mono">{settings?.season}</div>
</div>
</div>
</div>
</section>
{/* API Statistics */}
<section className="bg-gray-800/50 backdrop-blur-sm rounded-xl border border-gray-700 p-6">
<h2 className="text-lg font-semibold text-white flex items-center gap-2 mb-6">
<BarChart3 className="h-5 w-5 text-purple-500" />
API Statistics
</h2>
<div className="grid grid-cols-2 gap-4 mb-6">
<div className="bg-gray-700/50 rounded-lg p-4">
<div className="text-3xl font-bold text-white">
{stats?.totalRequests || 0}
</div>
<div className="text-sm text-gray-400">Total Requests</div>
</div>
<div className="bg-gray-700/50 rounded-lg p-4">
<div className="text-3xl font-bold text-white">
{stats?.requestsToday || 0}
</div>
<div className="text-sm text-gray-400">Requests Today</div>
</div>
<div className="bg-gray-700/50 rounded-lg p-4">
<div className="text-3xl font-bold text-white">
{stats?.avgResponseTime || 0}ms
</div>
<div className="text-sm text-gray-400">Avg Response Time</div>
</div>
<div className="bg-gray-700/50 rounded-lg p-4">
<div className="text-3xl font-bold text-green-400">
{stats?.successRate || 100}%
</div>
<div className="text-sm text-gray-400">Success Rate</div>
</div>
</div>
<ApiStatsChart data={stats?.chartData || []} />
{stats?.requestsByEndpoint && stats.requestsByEndpoint.length > 0 && (
<div className="mt-6">
<h3 className="text-sm font-medium text-gray-300 mb-3">
Requests by Endpoint
</h3>
<div className="space-y-2">
{stats.requestsByEndpoint.map((item) => (
<div
key={item.endpoint}
className="flex items-center justify-between bg-gray-700/50 rounded-lg px-3 py-2"
>
<span className="text-gray-300 font-mono text-sm">
{item.endpoint}
</span>
<span className="text-white font-semibold">
{item.count}
</span>
</div>
))}
</div>
</div>
)}
</section>
</div>
</main>
</div>
);
}