Files
tt-booking/components/admin/admin-dashboard.tsx
T
mikicvi 40c56770a2 feat: implement admin blocks management feature
- Added AdminBlocksManagement component for managing court blocks.
- Implemented functionality to create, edit, and delete blocks.
- Integrated fetching of courts and blocks from the API.
- Added validation for block creation and editing forms.
- Enhanced UI with responsive design for mobile and desktop views.
- Created database migration for court_blocks table and updated users table with theme_preference.
2025-12-29 17:04:16 +00:00

295 lines
9.4 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Badge } from '@/components/ui/badge';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
Users,
Calendar,
Settings,
BarChart3,
Bell,
Shield,
Clock,
MapPin,
Activity,
LogOut,
ArrowLeft,
ChevronDown,
} from 'lucide-react';
import { useRouter } from 'next/navigation';
import { AdminUserManagement } from './AdminUserManagement';
import { AdminAnnouncementManagement } from './AdminAnnouncementManagement';
import { AdminLogs } from './AdminLogs';
import { AdminRecentBookings } from './AdminRecentBookings';
import { AdminCourtManagement } from './AdminCourtManagement';
import { AdminSettingsManagement } from './AdminSettingsManagement';
import { AdminTimeSlotManagement } from './AdminTimeSlotManagement';
import { ModeToggle } from '@/components/ui/mode-toggle';
interface AdminStats {
totalUsers: number;
activeCourts: number;
todaysBookings: number;
monthlyBookings: number;
}
interface RecentBooking {
id: string;
date: string;
startTime: string;
endTime: string;
courtName: string;
userName: string;
status: string;
}
export function AdminDashboard() {
const router = useRouter();
const [activeTab, setActiveTab] = useState('bookings');
const [stats, setStats] = useState<AdminStats>({
totalUsers: 0,
activeCourts: 0,
todaysBookings: 0,
monthlyBookings: 0,
});
const [recentBookings, setRecentBookings] = useState<RecentBooking[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchStats();
}, []);
const fetchStats = async () => {
try {
const response = await fetch('/api/admin/stats');
if (response.ok) {
const data = await response.json();
setStats(data.stats);
setRecentBookings(data.recentBookings);
}
} catch (error) {
console.error('Error fetching admin stats:', error);
} finally {
setLoading(false);
}
};
const handleLogout = async () => {
try {
await fetch('/api/auth/logout', {
method: 'POST',
});
router.push('/');
} catch (error) {
console.error('Logout error:', error);
}
};
return (
<div className='min-h-screen bg-background'>
{/* Header */}
<header className='bg-card border-b border-border'>
<div className='container mx-auto px-4'>
{/* Desktop Layout */}
<div className='hidden md:flex items-center justify-between h-16'>
<div className='flex items-center space-x-4'>
<Shield className='h-6 w-6 text-blue-600 dark:text-blue-400' />
<h1 className='text-xl font-semibold text-foreground'>Admin Dashboard</h1>
</div>
<div className='flex items-center space-x-4'>
<Button
variant='ghost'
size='sm'
onClick={() => router.push('/dashboard')}
className='flex items-center gap-2'
>
<ArrowLeft className='h-4 w-4' />
Back to Booking
</Button>
<Badge variant='secondary'>Administrator</Badge>
<ModeToggle />
<Button variant='ghost' size='sm' onClick={handleLogout}>
<LogOut className='h-4 w-4' />
Logout
</Button>
</div>
</div>
{/* Mobile Layout */}
<div className='md:hidden py-3'>
{/* Top row */}
<div className='flex items-center justify-between mb-3'>
<div className='flex items-center space-x-2 min-w-0 flex-1'>
<Shield className='h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0' />
<h1 className='text-lg font-semibold text-foreground truncate'>Admin Dashboard</h1>
<Badge variant='secondary' className='flex-shrink-0'>
Admin
</Badge>
</div>
<ModeToggle />
</div>
{/* Bottom row */}
<div className='flex items-center justify-between gap-2'>
<Button
variant='ghost'
size='sm'
onClick={() => router.push('/dashboard')}
className='flex items-center gap-1 px-3 py-2 min-h-[36px] touch-manipulation'
>
<ArrowLeft className='h-4 w-4' />
<span className='text-xs font-medium'>Booking</span>
</Button>
<Button
variant='ghost'
size='sm'
onClick={handleLogout}
className='flex items-center gap-1 px-3 py-2 min-h-[36px] touch-manipulation'
>
<LogOut className='h-4 w-4' />
<span className='text-xs font-medium'>Logout</span>
</Button>
</div>
</div>
</div>
</header>
<main className='container mx-auto px-4 py-8'>
{/* Stats Cards */}
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8'>
<Card>
<CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
<CardTitle className='text-sm font-medium'>Total Users</CardTitle>
<Users className='h-4 w-4 text-muted-foreground' />
</CardHeader>
<CardContent>
<div className='text-2xl font-bold'>{loading ? '...' : stats.totalUsers}</div>
<p className='text-xs text-muted-foreground'>Registered users</p>
</CardContent>
</Card>
<Card>
<CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
<CardTitle className='text-sm font-medium'>Active Courts</CardTitle>
<MapPin className='h-4 w-4 text-muted-foreground' />
</CardHeader>
<CardContent>
<div className='text-2xl font-bold'>{loading ? '...' : stats.activeCourts}</div>
<p className='text-xs text-muted-foreground'>Available for booking</p>
</CardContent>
</Card>
<Card>
<CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
<CardTitle className='text-sm font-medium'>Today's Bookings</CardTitle>
<Calendar className='h-4 w-4 text-muted-foreground' />
</CardHeader>
<CardContent>
<div className='text-2xl font-bold'>{loading ? '...' : stats.todaysBookings}</div>
<p className='text-xs text-muted-foreground'>Bookings for today</p>
</CardContent>
</Card>
<Card>
<CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>
<CardTitle className='text-sm font-medium'>Monthly Bookings</CardTitle>
<BarChart3 className='h-4 w-4 text-muted-foreground' />
</CardHeader>
<CardContent>
<div className='text-2xl font-bold'>{loading ? '...' : stats.monthlyBookings}</div>
<p className='text-xs text-muted-foreground'>This month's total</p>
</CardContent>
</Card>
</div>
{/* Admin Tabs */}
<Tabs value={activeTab} onValueChange={setActiveTab} className='space-y-6'>
{/* Desktop Tabs */}
<TabsList className='hidden md:grid w-full grid-cols-6'>
<TabsTrigger value='bookings'>Bookings</TabsTrigger>
<TabsTrigger value='users'>Users</TabsTrigger>
<TabsTrigger value='courts'>Courts</TabsTrigger>
<TabsTrigger value='settings'>Settings</TabsTrigger>
<TabsTrigger value='announcements'>Announcements</TabsTrigger>
<TabsTrigger value='logs'>Logs</TabsTrigger>
</TabsList>
{/* Mobile Dropdown */}
<div className='md:hidden'>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant='outline' className='w-full justify-between'>
{activeTab === 'bookings' && 'Bookings'}
{activeTab === 'users' && 'Users'}
{activeTab === 'courts' && 'Courts'}
{activeTab === 'settings' && 'Settings'}
{activeTab === 'announcements' && 'Announcements'}
{activeTab === 'logs' && 'Logs'}
<ChevronDown className='h-4 w-4' />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className='w-full'>
<DropdownMenuItem onClick={() => setActiveTab('bookings')}>
<Calendar className='h-4 w-4 mr-2' />
Bookings
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setActiveTab('users')}>
<Users className='h-4 w-4 mr-2' />
Users
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setActiveTab('courts')}>
<MapPin className='h-4 w-4 mr-2' />
Courts
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setActiveTab('settings')}>
<Settings className='h-4 w-4 mr-2' />
Settings
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setActiveTab('announcements')}>
<Bell className='h-4 w-4 mr-2' />
Announcements
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setActiveTab('logs')}>
<Activity className='h-4 w-4 mr-2' />
Logs
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<TabsContent value='bookings'>
<AdminRecentBookings />
</TabsContent>
<TabsContent value='users'>
<AdminUserManagement />
</TabsContent>
<TabsContent value='courts'>
<AdminCourtManagement />
</TabsContent>
<TabsContent value='settings'>
<div className='space-y-6'>
<AdminSettingsManagement />
<AdminTimeSlotManagement />
</div>
</TabsContent>
<TabsContent value='announcements'>
<AdminAnnouncementManagement />
</TabsContent>{' '}
<TabsContent value='logs'>
<AdminLogs />
</TabsContent>
</Tabs>
</main>
</div>
);
}