You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
9.2 KiB
TypeScript
263 lines
9.2 KiB
TypeScript
'use client';
|
|
|
|
import { Building, AlertCircle, Lock, Loader2, ArrowLeft, MapPin, Calendar, Server } from 'lucide-react';
|
|
import Link from 'next/link';
|
|
import { useEffect, useState } from 'react';
|
|
import { getAdminClubDetail } from '@/src/lib/api/admin-clubs';
|
|
import type { AdminClubDetail, AdminApiError } from '@/src/types/admin-api';
|
|
import useTranslation from '@/src/hooks/useTranslation';
|
|
|
|
interface AdminClubDetailProps {
|
|
clubId: number;
|
|
}
|
|
|
|
export default function AdminClubDetailComponent({ clubId }: AdminClubDetailProps) {
|
|
const { t, locale } = useTranslation();
|
|
const [clubDetail, setClubDetail] = useState<AdminClubDetail | null>(null);
|
|
const [error, setError] = useState<AdminApiError | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
async function loadClubDetail() {
|
|
setLoading(true);
|
|
const result = await getAdminClubDetail(clubId);
|
|
|
|
if (result.success) {
|
|
setClubDetail(result.data);
|
|
setError(null);
|
|
} else {
|
|
setError(result.error);
|
|
setClubDetail(null);
|
|
}
|
|
|
|
setLoading(false);
|
|
}
|
|
|
|
loadClubDetail();
|
|
}, [clubId]);
|
|
|
|
// Loading state
|
|
if (loading) {
|
|
return (
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
|
<div className="flex flex-col items-center justify-center space-y-4">
|
|
<Loader2 className="w-12 h-12 text-indigo-600 animate-spin" />
|
|
<p className="text-gray-600">{t('Loading club details...')}</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Authentication error (401)
|
|
if (error && error.status === 401) {
|
|
return (
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
|
<div className="max-w-2xl mx-auto">
|
|
<div className="bg-yellow-50 border border-yellow-200 rounded-xl p-8">
|
|
<div className="flex items-start space-x-4">
|
|
<Lock className="w-8 h-8 text-yellow-600 flex-shrink-0 mt-1" />
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-4">
|
|
{t('Authentication Required')}
|
|
</h2>
|
|
<p className="text-gray-700 mb-4">
|
|
{t('Please log in to access the venue management portal.')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Forbidden error (403)
|
|
if (error && error.status === 403) {
|
|
return (
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
|
<div className="max-w-2xl mx-auto">
|
|
<div className="bg-red-50 border border-red-200 rounded-xl p-8">
|
|
<div className="flex items-start space-x-4">
|
|
<Lock className="w-8 h-8 text-red-600 flex-shrink-0 mt-1" />
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-4">
|
|
{t('Access Denied')}
|
|
</h2>
|
|
<p className="text-gray-700 mb-4">
|
|
{error.detail}
|
|
</p>
|
|
<Link
|
|
href={`/${locale}/admin/clubs`}
|
|
className="inline-flex items-center text-indigo-600 hover:text-indigo-700 font-medium"
|
|
>
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
{t('Back to clubs')}
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Other API errors
|
|
if (error) {
|
|
return (
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
|
<div className="max-w-2xl mx-auto">
|
|
<div className="bg-red-50 border border-red-200 rounded-xl p-8">
|
|
<div className="flex items-start space-x-4">
|
|
<AlertCircle className="w-8 h-8 text-red-600 flex-shrink-0 mt-1" />
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-4">
|
|
{t('Error Loading Club')}
|
|
</h2>
|
|
<p className="text-gray-700 mb-4">
|
|
{error.detail}
|
|
</p>
|
|
<p className="text-sm text-gray-600 font-mono mb-4">
|
|
{t('Error code')}: {error.code}
|
|
</p>
|
|
<Link
|
|
href={`/${locale}/admin/clubs`}
|
|
className="inline-flex items-center text-indigo-600 hover:text-indigo-700 font-medium"
|
|
>
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
{t('Back to clubs')}
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!clubDetail) {
|
|
return null;
|
|
}
|
|
|
|
// Success - render club detail
|
|
return (
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
{/* Breadcrumb */}
|
|
<div className="mb-6">
|
|
<Link
|
|
href={`/${locale}/admin/clubs`}
|
|
className="inline-flex items-center text-gray-600 hover:text-gray-900"
|
|
>
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
{t('Back to clubs')}
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Club Header */}
|
|
<div className="bg-white border border-gray-200 rounded-xl p-6 mb-6">
|
|
<div className="flex items-start space-x-4">
|
|
<div className="p-3 bg-indigo-100 rounded-lg">
|
|
<Building className="w-8 h-8 text-indigo-600" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
|
{clubDetail.club.name}
|
|
</h1>
|
|
<div className="flex items-center text-gray-600 space-x-4">
|
|
<div className="flex items-center">
|
|
<MapPin className="w-4 h-4 mr-1" />
|
|
<span>{clubDetail.club.timezone}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Provider Info */}
|
|
<div className="bg-white border border-gray-200 rounded-xl p-6 mb-6">
|
|
<div className="flex items-center space-x-3 mb-4">
|
|
<Server className="w-5 h-5 text-gray-600" />
|
|
<h2 className="text-xl font-bold text-gray-900">{t('Provider Information')}</h2>
|
|
</div>
|
|
<div className="space-y-3">
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">{t('Type')}:</span>
|
|
<span className="font-medium text-gray-900">{clubDetail.provider.remote_type}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">{t('Manages Slot Storage')}:</span>
|
|
<span className="font-medium text-gray-900">
|
|
{clubDetail.provider.capabilities.manages_slot_storage ? t('Yes') : t('No')}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">{t('Supports Payment Verification')}:</span>
|
|
<span className="font-medium text-gray-900">
|
|
{clubDetail.provider.capabilities.supports_payment_verification ? t('Yes') : t('No')}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Courts */}
|
|
<div className="bg-white border border-gray-200 rounded-xl p-6 mb-6">
|
|
<h2 className="text-xl font-bold text-gray-900 mb-4">{t('Courts')}</h2>
|
|
{clubDetail.courts.length === 0 ? (
|
|
<p className="text-gray-600">{t('No courts configured')}</p>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{clubDetail.courts.map((court) => (
|
|
<div
|
|
key={court.court_id}
|
|
className="border border-gray-200 rounded-lg p-4"
|
|
>
|
|
<p className="font-medium text-gray-900">{court.name}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Slot Definitions */}
|
|
<div className="bg-white border border-gray-200 rounded-xl p-6 mb-6">
|
|
<div className="flex items-center space-x-3 mb-4">
|
|
<Calendar className="w-5 h-5 text-gray-600" />
|
|
<h2 className="text-xl font-bold text-gray-900">{t('Slot Definitions')}</h2>
|
|
</div>
|
|
{clubDetail.slot_definitions.length === 0 ? (
|
|
<p className="text-gray-600">{t('No slot definitions configured')}</p>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{clubDetail.slot_definitions.map((slotDef, index) => (
|
|
<div
|
|
key={index}
|
|
className="border border-gray-200 rounded-lg p-4"
|
|
>
|
|
<p className="text-sm text-gray-600">{t('Slot definition')} #{index + 1}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Upcoming Slots */}
|
|
<div className="bg-white border border-gray-200 rounded-xl p-6">
|
|
<h2 className="text-xl font-bold text-gray-900 mb-4">{t('Upcoming Slots')}</h2>
|
|
{clubDetail.upcoming_slots.length === 0 ? (
|
|
<p className="text-gray-600">{t('No upcoming slots available')}</p>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{clubDetail.upcoming_slots.map((slot, index) => (
|
|
<div
|
|
key={index}
|
|
className="border border-gray-200 rounded-lg p-4"
|
|
>
|
|
<p className="text-sm text-gray-600">{t('Slot')} #{index + 1}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|