Files
tt-booking/components/admin/AdminSettingsManagement.tsx
T

317 lines
9.1 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 { Input } from '@/components/ui/input';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { toast } from '@/hooks/use-toast';
import { Settings, Save, RefreshCw } from 'lucide-react';
interface Setting {
id: string;
key: string;
value: string;
updatedAt: Date;
}
interface SettingsData {
booking_window_days: string;
max_booking_duration_hours: string;
min_booking_duration_minutes: string;
booking_start_time: string;
booking_end_time: string;
allow_weekend_bookings: string;
max_bookings_per_user_per_hour_per_day: string;
}
export function AdminSettingsManagement() {
const [settings, setSettings] = useState<SettingsData>({
booking_window_days: '7',
max_booking_duration_hours: '2',
min_booking_duration_minutes: '30',
booking_start_time: '08:00',
booking_end_time: '22:00',
allow_weekend_bookings: 'true',
max_bookings_per_user_per_hour_per_day: '1',
});
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
useEffect(() => {
fetchSettings();
}, []);
const fetchSettings = async () => {
try {
setLoading(true);
const response = await fetch('/api/admin/settings');
if (response.ok) {
const data = await response.json();
const settingsMap: SettingsData = {
booking_window_days: '7',
max_booking_duration_hours: '2',
min_booking_duration_minutes: '30',
booking_start_time: '08:00',
booking_end_time: '22:00',
allow_weekend_bookings: 'true',
max_bookings_per_user_per_hour_per_day: '1',
};
// Map the settings array to our object
data.settings?.forEach((setting: Setting) => {
if (setting.key in settingsMap) {
settingsMap[setting.key as keyof SettingsData] = setting.value;
}
});
setSettings(settingsMap);
} else {
toast({
title: 'Error',
description: 'Failed to fetch settings',
variant: 'destructive',
});
}
} catch (error) {
console.error('Error fetching settings:', error);
toast({
title: 'Error',
description: 'Failed to fetch settings',
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
const handleSave = async () => {
try {
setSaving(true);
// Convert settings object to array format
const settingsArray = Object.entries(settings).map(([key, value]) => ({
key,
value,
}));
const response = await fetch('/api/admin/settings', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ settings: settingsArray }),
});
if (response.ok) {
toast({
title: 'Success',
description: 'Settings updated successfully',
});
await fetchSettings();
} else {
const error = await response.json();
toast({
title: 'Error',
description: error.error || 'Failed to update settings',
variant: 'destructive',
});
}
} catch (error) {
console.error('Error updating settings:', error);
toast({
title: 'Error',
description: 'Failed to update settings',
variant: 'destructive',
});
} finally {
setSaving(false);
}
};
const updateSetting = (key: keyof SettingsData, value: string) => {
setSettings((prev) => ({ ...prev, [key]: value }));
};
if (loading) {
return (
<Card>
<CardHeader>
<CardTitle className='flex items-center gap-2'>
<Settings className='h-5 w-5' />
System Settings
</CardTitle>
</CardHeader>
<CardContent>
<div className='space-y-4'>
{[1, 2, 3, 4].map((i) => (
<div key={i} className='animate-pulse'>
<div className='h-4 bg-gray-200 rounded w-1/4 mb-2'></div>
<div className='h-10 bg-gray-200 rounded w-1/2'></div>
</div>
))}
</div>
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader className='flex flex-row items-center justify-between'>
<CardTitle className='flex items-center gap-2'>
<Settings className='h-5 w-5' />
System Settings
</CardTitle>
<div className='flex gap-2'>
<Button size='sm' variant='outline' onClick={fetchSettings}>
<RefreshCw className='h-4 w-4 mr-2' />
Refresh
</Button>
<Button size='sm' onClick={handleSave} disabled={saving}>
{saving ? (
<>
<RefreshCw className='h-4 w-4 mr-2 animate-spin' />
Saving...
</>
) : (
<>
<Save className='h-4 w-4 mr-2' />
Save Changes
</>
)}
</Button>
</div>
</CardHeader>
<CardContent className='space-y-6'>
<div className='grid grid-cols-1 md:grid-cols-2 gap-6'>
{/* Booking Window */}
<div className='space-y-2'>
<Label htmlFor='booking_window_days'>Booking Window (days)</Label>
<Input
id='booking_window_days'
type='number'
min='1'
max='30'
value={settings.booking_window_days}
onChange={(e) => updateSetting('booking_window_days', e.target.value)}
/>
<p className='text-sm text-gray-500'>How many days in advance users can book</p>
</div>
{/* Max Duration */}
<div className='space-y-2'>
<Label htmlFor='max_booking_duration_hours'>Max Booking Duration (hours)</Label>
<Input
id='max_booking_duration_hours'
type='number'
min='0.5'
max='8'
step='0.5'
value={settings.max_booking_duration_hours}
onChange={(e) => updateSetting('max_booking_duration_hours', e.target.value)}
/>
<p className='text-sm text-gray-500'>Maximum hours per booking session</p>
</div>
{/* Min Duration */}
<div className='space-y-2'>
<Label htmlFor='min_booking_duration_minutes'>Min Booking Duration (minutes)</Label>
<Input
id='min_booking_duration_minutes'
type='number'
min='15'
max='120'
step='15'
value={settings.min_booking_duration_minutes}
onChange={(e) => updateSetting('min_booking_duration_minutes', e.target.value)}
/>
<p className='text-sm text-gray-500'>Minimum minutes per booking session</p>
</div>
{/* Start Time */}
<div className='space-y-2'>
<Label htmlFor='booking_start_time'>Daily Start Time</Label>
<Input
id='booking_start_time'
type='time'
value={settings.booking_start_time}
onChange={(e) => updateSetting('booking_start_time', e.target.value)}
/>
<p className='text-sm text-gray-500'>When courts open for booking each day</p>
</div>
{/* End Time */}
<div className='space-y-2'>
<Label htmlFor='booking_end_time'>Daily End Time</Label>
<Input
id='booking_end_time'
type='time'
value={settings.booking_end_time}
onChange={(e) => updateSetting('booking_end_time', e.target.value)}
/>
<p className='text-sm text-gray-500'>When courts close for booking each day</p>
</div>
{/* Booking Restrictions */}
<div className='space-y-2'>
<Label htmlFor='max_bookings_per_user_per_hour_per_day'>Max Bookings per User per Hour</Label>
<Input
id='max_bookings_per_user_per_hour_per_day'
type='number'
min='1'
max='5'
value={settings.max_bookings_per_user_per_hour_per_day}
onChange={(e) => updateSetting('max_bookings_per_user_per_hour_per_day', e.target.value)}
/>
<p className='text-sm text-gray-500'>Maximum bookings per user per hour on the same day</p>
</div>
{/* Weekend Bookings */}
<div className='space-y-2'>
<div className='flex items-center space-x-2'>
<Switch
id='allow_weekend_bookings'
checked={settings.allow_weekend_bookings === 'true'}
onCheckedChange={(checked: boolean) =>
updateSetting('allow_weekend_bookings', checked.toString())
}
/>
<Label htmlFor='allow_weekend_bookings'>Allow Weekend Bookings</Label>
</div>
<p className='text-sm text-gray-500'>Whether users can book courts on weekends</p>
</div>
</div>
<div className='border-t pt-6'>
<h3 className='text-lg font-medium mb-4'>Current Configuration Summary</h3>
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 text-sm'>
<div className='space-y-2'>
<p>
<strong>Booking Window:</strong> {settings.booking_window_days} days
</p>
<p>
<strong>Session Duration:</strong> {settings.min_booking_duration_minutes}min -{' '}
{settings.max_booking_duration_hours}hrs
</p>
<p>
<strong>Operating Hours:</strong> {settings.booking_start_time} -{' '}
{settings.booking_end_time}
</p>
</div>
<div className='space-y-2'>
<p>
<strong>Weekend Bookings:</strong>{' '}
{settings.allow_weekend_bookings === 'true' ? 'Enabled' : 'Disabled'}
</p>
<p>
<strong>Booking Limit:</strong> {settings.max_bookings_per_user_per_hour_per_day} per
hour
</p>
</div>
</div>
</div>
</CardContent>
</Card>
);
}