'use client'; import { useState, useEffect, useCallback } from 'react'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } 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 { Switch } from '@/components/ui/switch'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog'; import { Badge } from '@/components/ui/badge'; import { Calendar, Trash2, Plus, Ban, CalendarX, Edit, Bell } from 'lucide-react'; import { useToast } from '@/hooks/use-toast'; interface Court { id: string; name: string; } interface CourtBlock { id: string; courtId: string | null; courtName: string | null; date: string; startTime: string; endTime: string; reason: string; createdBy: string; creatorName: string; createdAt: string; } export function AdminBlocksManagement() { const { toast } = useToast(); const [blocks, setBlocks] = useState([]); const [courts, setCourts] = useState([]); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); // Form state for creating const [selectedCourt, setSelectedCourt] = useState('all'); const [blockDate, setBlockDate] = useState(''); const [startTime, setStartTime] = useState('18:00'); const [endTime, setEndTime] = useState('23:00'); const [reason, setReason] = useState(''); const [createAnnouncement, setCreateAnnouncement] = useState(true); // Edit modal state const [editingBlock, setEditingBlock] = useState(null); const [editCourt, setEditCourt] = useState('all'); const [editDate, setEditDate] = useState(''); const [editStartTime, setEditStartTime] = useState(''); const [editEndTime, setEditEndTime] = useState(''); const [editReason, setEditReason] = useState(''); // Generate time options (e.g., 06:00 to 23:00) const timeOptions = []; for (let h = 6; h <= 23; h++) { timeOptions.push(`${String(h).padStart(2, '0')}:00`); } const fetchBlocks = useCallback(async () => { try { const response = await fetch('/api/admin/blocks'); if (response.ok) { const data = await response.json(); setBlocks(data.blocks || []); } else { toast({ title: 'Error', description: 'Failed to fetch blocks', variant: 'destructive', }); } } catch (error) { console.error('Error fetching blocks:', error); toast({ title: 'Error', description: 'Failed to fetch blocks', variant: 'destructive', }); } finally { setLoading(false); } }, [toast]); const fetchCourts = useCallback(async () => { try { const response = await fetch('/api/courts'); if (response.ok) { const data = await response.json(); setCourts(data.courts || []); } } catch (error) { console.error('Error fetching courts:', error); } }, []); useEffect(() => { fetchBlocks(); fetchCourts(); }, [fetchBlocks, fetchCourts]); const handleCreateBlock = async (e: React.FormEvent) => { e.preventDefault(); if (!blockDate) { toast({ title: 'Validation Error', description: 'Please select a date', variant: 'destructive', }); return; } if (!reason.trim()) { toast({ title: 'Validation Error', description: 'Please provide a reason for the block', variant: 'destructive', }); return; } if (startTime >= endTime) { toast({ title: 'Validation Error', description: 'End time must be after start time', variant: 'destructive', }); return; } setSubmitting(true); try { const response = await fetch('/api/admin/blocks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ courtId: selectedCourt === 'all' ? null : selectedCourt, date: blockDate, startTime, endTime, reason: reason.trim(), createAnnouncement, }), }); if (response.ok) { const data = await response.json(); toast({ title: 'Success', description: data.announcementCreated ? 'Block created and announcement posted' : 'Block created successfully', }); // Reset form setBlockDate(''); setReason(''); setSelectedCourt('all'); setStartTime('18:00'); setEndTime('22:00'); setCreateAnnouncement(true); // Refresh blocks list fetchBlocks(); } else { const data = await response.json(); toast({ title: 'Error', description: data.error || 'Failed to create block', variant: 'destructive', }); } } catch (error) { console.error('Error creating block:', error); toast({ title: 'Error', description: 'Failed to create block', variant: 'destructive', }); } finally { setSubmitting(false); } }; const handleEditClick = (block: CourtBlock) => { setEditingBlock(block); setEditCourt(block.courtId || 'all'); setEditDate(block.date); setEditStartTime(block.startTime); setEditEndTime(block.endTime); setEditReason(block.reason); }; const handleEditSave = async () => { if (!editingBlock) return; if (!editDate || !editReason.trim()) { toast({ title: 'Validation Error', description: 'Please fill in all required fields', variant: 'destructive', }); return; } if (editStartTime >= editEndTime) { toast({ title: 'Validation Error', description: 'End time must be after start time', variant: 'destructive', }); return; } setSubmitting(true); try { const response = await fetch(`/api/admin/blocks/${editingBlock.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ courtId: editCourt === 'all' ? null : editCourt, date: editDate, startTime: editStartTime, endTime: editEndTime, reason: editReason.trim(), }), }); if (response.ok) { toast({ title: 'Success', description: 'Block updated successfully', }); setEditingBlock(null); fetchBlocks(); } else { const data = await response.json(); toast({ title: 'Error', description: data.error || 'Failed to update block', variant: 'destructive', }); } } catch (error) { console.error('Error updating block:', error); toast({ title: 'Error', description: 'Failed to update block', variant: 'destructive', }); } finally { setSubmitting(false); } }; const handleDeleteBlock = async (blockId: string) => { try { const response = await fetch(`/api/admin/blocks/${blockId}`, { method: 'DELETE', }); if (response.ok) { toast({ title: 'Success', description: 'Block removed successfully', }); fetchBlocks(); } else { const data = await response.json(); toast({ title: 'Error', description: data.error || 'Failed to delete block', variant: 'destructive', }); } } catch (error) { console.error('Error deleting block:', error); toast({ title: 'Error', description: 'Failed to delete block', variant: 'destructive', }); } }; // Get min date (today) and max date (e.g., 12 weeks from now) const today = new Date(); const minDate = today.toISOString().split('T')[0]; const maxDate = new Date(today.getTime() + 84 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; // 12 weeks // Format date for display const formatDate = (dateStr: string) => { const date = new Date(dateStr); return date.toLocaleDateString('en-IE', { weekday: 'short', day: 'numeric', month: 'short', year: 'numeric', }); }; // Check if a block is in the past const isBlockPast = (dateStr: string) => { const blockDate = new Date(dateStr); blockDate.setHours(23, 59, 59, 999); return blockDate < new Date(); }; // Sort blocks by date const sortedBlocks = [...blocks].sort((a, b) => { const dateA = new Date(a.date); const dateB = new Date(b.date); return dateA.getTime() - dateB.getTime(); }); return (
{/* Create Block Form */} Block Courts/Hall Block court availability for tournaments, AGM, maintenance, or other events. Blocked slots will be visible to members but cannot be booked.
{/* Court Selection */}
{/* Date Selection */}
setBlockDate(e.target.value)} min={minDate} max={maxDate} required />
{/* Reason */}
setReason(e.target.value)} maxLength={200} required />
{/* Start Time */}
{/* End Time */}
{/* Create Announcement Toggle */}
{createAnnouncement && (

An announcement will be created and automatically expire when the block ends.

)} {/* Submit Button */}
{/* Existing Blocks List */} Scheduled Blocks Upcoming and current court blocks. Past blocks are shown for reference. {loading ? (
Loading blocks...
) : sortedBlocks.length === 0 ? (
No blocks scheduled. Create a block above to reserve courts for events.
) : ( <> {/* Desktop Table */}
Date Court Time Reason Created By Actions {sortedBlocks.map((block) => (
{formatDate(block.date)} {isBlockPast(block.date) && ( Past )}
{block.courtName ? ( {block.courtName} ) : ( All Courts )} {block.startTime} - {block.endTime} {block.reason} {block.creatorName}
{!isBlockPast(block.date) && ( )} Remove Block? This will remove the block for{' '} {formatDate(block.date)} ({block.reason}). Members will be able to book this time slot again. Cancel handleDeleteBlock(block.id)} > Remove Block
))}
{/* Mobile Cards */}
{sortedBlocks.map((block) => (
{formatDate(block.date)} {isBlockPast(block.date) && ( Past )}
{!isBlockPast(block.date) && ( )} Remove Block? This will remove the block for{' '} {formatDate(block.date)} ({block.reason}). Cancel handleDeleteBlock(block.id)} > Remove Block
Court: {block.courtName ? ( {block.courtName} ) : ( All Courts )}
Time: {block.startTime} - {block.endTime}
Reason: {block.reason}
Created by {block.creatorName}
))}
)}
{/* Edit Block Dialog */} !open && setEditingBlock(null)}> Edit Block Modify the court closure details.
setEditDate(e.target.value)} min={new Date().toISOString().split('T')[0]} />
setEditReason(e.target.value)} placeholder='e.g., Tournament, Maintenance' />
); }