fixes, theming, branding

This commit is contained in:
mikicvi
2025-09-26 22:16:34 +01:00
parent 22c462c61c
commit 220f999f19
24 changed files with 787 additions and 260 deletions
@@ -8,6 +8,17 @@ import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { Textarea } from '@/components/ui/textarea';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { useToast } from '@/hooks/use-toast';
@@ -37,7 +48,9 @@ export function AdminAnnouncementManagement() {
const [loading, setLoading] = useState(true);
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [editingAnnouncement, setEditingAnnouncement] = useState<Announcement | null>(null);
const [announcementToDelete, setAnnouncementToDelete] = useState<Announcement | null>(null);
const [formData, setFormData] = useState<AnnouncementFormData>({
title: '',
content: '',
@@ -176,12 +189,21 @@ export function AdminAnnouncementManagement() {
}
};
const openDeleteDialog = (announcement: Announcement) => {
setAnnouncementToDelete(announcement);
setIsDeleteDialogOpen(true);
};
const confirmDeleteAnnouncement = async () => {
if (announcementToDelete) {
await handleDeleteAnnouncement(announcementToDelete.id);
setIsDeleteDialogOpen(false);
setAnnouncementToDelete(null);
}
};
const handleDeleteAnnouncement = async (announcementId: string) => {
try {
if (!confirm('Are you sure you want to delete this announcement?')) {
return;
}
const response = await fetch(`/api/admin/announcements/${announcementId}`, {
method: 'DELETE',
});
@@ -444,7 +466,7 @@ export function AdminAnnouncementManagement() {
<Button
variant='outline'
size='sm'
onClick={() => handleDeleteAnnouncement(announcement.id)}
onClick={() => openDeleteDialog(announcement)}
className='text-destructive hover:text-destructive/90'
>
<Trash2 className='h-4 w-4' />
@@ -539,6 +561,29 @@ export function AdminAnnouncementManagement() {
</div>
</DialogContent>
</Dialog>
{/* Delete Confirmation Dialog */}
<AlertDialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete{' '}
{announcementToDelete ? `"${announcementToDelete.title}"` : 'this announcement'}? This
action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={confirmDeleteAnnouncement}
className='bg-destructive hover:bg-destructive/90'
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}
+49 -5
View File
@@ -6,6 +6,17 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Switch } from '@/components/ui/switch';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } 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 { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
@@ -32,7 +43,9 @@ export function AdminCourtManagement() {
const [editing, setEditing] = useState<string | null>(null);
const [deleting, setDeleting] = useState<string | null>(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [editingCourt, setEditingCourt] = useState<Court | null>(null);
const [courtToDelete, setCourtToDelete] = useState<Court | null>(null);
const [formData, setFormData] = useState<CourtFormData>({
name: '',
isActive: true,
@@ -150,11 +163,20 @@ export function AdminCourtManagement() {
setIsDialogOpen(true);
};
const handleDelete = async (courtId: string) => {
if (!confirm('Are you sure you want to delete this court? This action cannot be undone.')) {
return;
}
const openDeleteDialog = (court: Court) => {
setCourtToDelete(court);
setIsDeleteDialogOpen(true);
};
const confirmDeleteCourt = async () => {
if (courtToDelete) {
await handleDelete(courtToDelete.id);
setIsDeleteDialogOpen(false);
setCourtToDelete(null);
}
};
const handleDelete = async (courtId: string) => {
try {
setDeleting(courtId);
const response = await fetch(`/api/admin/courts/${courtId}`, {
@@ -319,7 +341,7 @@ export function AdminCourtManagement() {
<Button
size='sm'
variant='outline'
onClick={() => handleDelete(court.id)}
onClick={() => openDeleteDialog(court)}
disabled={deleting === court.id}
className='text-red-600 hover:text-red-700'
>
@@ -337,6 +359,28 @@ export function AdminCourtManagement() {
</div>
)}
</CardContent>
{/* Delete Confirmation Dialog */}
<AlertDialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete {courtToDelete ? `"${courtToDelete.name}"` : 'this court'}?
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={confirmDeleteCourt}
className='bg-destructive hover:bg-destructive/90'
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Card>
);
}
+206 -124
View File
@@ -17,6 +17,12 @@ interface Setting {
}
interface SettingsData {
// Club/Brand Settings
club_name: string;
sport_name: string;
app_title: string;
app_description: string;
// Booking Settings
booking_window_days: string;
max_booking_duration_hours: string;
min_booking_duration_minutes: string;
@@ -30,6 +36,12 @@ interface SettingsData {
export function AdminSettingsManagement() {
const [settings, setSettings] = useState<SettingsData>({
// Club/Brand Settings
club_name: 'TT Club',
sport_name: 'Table Tennis',
app_title: 'Table Tennis Booking System',
app_description: 'Book your table tennis court slots with ease',
// Booking Settings
booking_window_days: '7',
max_booking_duration_hours: '2',
min_booking_duration_minutes: '30',
@@ -38,7 +50,7 @@ export function AdminSettingsManagement() {
allow_weekend_bookings: 'true',
max_bookings_per_user_per_hour_per_day: '1',
allow_booking_modifications: 'true',
booking_modification_hours_before: '2',
booking_modification_hours_before: '1',
});
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
@@ -54,6 +66,12 @@ export function AdminSettingsManagement() {
if (response.ok) {
const data = await response.json();
const settingsMap: SettingsData = {
// Club/Brand Settings
club_name: 'TT Club',
sport_name: 'Table Tennis',
app_title: 'Table Tennis Booking System',
app_description: 'Book your table tennis court slots with ease',
// Booking Settings
booking_window_days: '7',
max_booking_duration_hours: '2',
min_booking_duration_minutes: '30',
@@ -62,10 +80,8 @@ export function AdminSettingsManagement() {
allow_weekend_bookings: 'true',
max_bookings_per_user_per_hour_per_day: '1',
allow_booking_modifications: 'true',
booking_modification_hours_before: '2',
};
// Map the settings array to our object
booking_modification_hours_before: '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;
@@ -189,139 +205,205 @@ export function AdminSettingsManagement() {
</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>
{/* Club/Brand Configuration Section */}
<div>
<h3 className='text-lg font-medium mb-4'>Club & Branding</h3>
<div className='grid grid-cols-1 md:grid-cols-2 gap-6'>
{/* Club Name */}
<div className='space-y-2'>
<Label htmlFor='club_name'>Club Name</Label>
<Input
id='club_name'
type='text'
placeholder='e.g., Downtown TT Club'
value={settings.club_name}
onChange={(e) => updateSetting('club_name', e.target.value)}
/>
<p className='text-sm text-gray-500'>The name of your club or organization</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>
{/* Sport Name */}
<div className='space-y-2'>
<Label htmlFor='sport_name'>Sport Name</Label>
<Input
id='sport_name'
type='text'
placeholder='e.g., Table Tennis, Ping Pong, Badminton'
value={settings.sport_name}
onChange={(e) => updateSetting('sport_name', e.target.value)}
/>
<p className='text-sm text-gray-500'>The sport played at your facility</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>
{/* App Title */}
<div className='space-y-2'>
<Label htmlFor='app_title'>Application Title</Label>
<Input
id='app_title'
type='text'
placeholder='e.g., Downtown TT Booking'
value={settings.app_title}
onChange={(e) => updateSetting('app_title', e.target.value)}
/>
<p className='text-sm text-gray-500'>Main title shown in browser and app header</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>
{/* App Description */}
<div className='space-y-2'>
<Label htmlFor='app_description'>Application Description</Label>
<Input
id='app_description'
type='text'
placeholder='e.g., Book your court slots with ease'
value={settings.app_description}
onChange={(e) => updateSetting('app_description', e.target.value)}
/>
<p className='text-sm text-gray-500'>Short description for login/register pages</p>
</div>
</div>
</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 Configuration Section */}
<div>
<h3 className='text-lg font-medium mb-4'>Booking Configuration</h3>
<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>
{/* 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>
{/* 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>
{/* Booking Modification Settings */}
<div className='space-y-2'>
<div className='flex items-center space-x-2'>
<Switch
id='allow_booking_modifications'
checked={settings.allow_booking_modifications === 'true'}
onCheckedChange={(checked: boolean) =>
updateSetting('allow_booking_modifications', checked.toString())
{/* 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)
}
/>
<Label htmlFor='allow_booking_modifications'>Allow Booking Modifications</Label>
<p className='text-sm text-gray-500'>Maximum bookings per user per hour on the same day</p>
</div>
<p className='text-sm text-gray-500'>Whether users can edit or cancel their bookings</p>
</div>
{/* Modification Time Restriction */}
<div className='space-y-2'>
<Label htmlFor='booking_modification_hours_before'>
Modification Time Limit (hours before session)
</Label>
<Input
id='booking_modification_hours_before'
type='number'
min='0.5'
max='48'
step='0.5'
value={settings.booking_modification_hours_before}
onChange={(e) => updateSetting('booking_modification_hours_before', e.target.value)}
disabled={settings.allow_booking_modifications !== 'true'}
/>
<p className='text-sm text-gray-500'>
{settings.allow_booking_modifications === 'true'
? 'How many hours before a session users can still modify bookings'
: 'Enable booking modifications to configure this setting'}
</p>
</div>
{/* Booking Modification Settings */}
<div className='space-y-2'>
<div className='flex items-center space-x-2'>
<Switch
id='allow_booking_modifications'
checked={settings.allow_booking_modifications === 'true'}
onCheckedChange={(checked: boolean) =>
updateSetting('allow_booking_modifications', checked.toString())
}
/>
<Label htmlFor='allow_booking_modifications'>Allow Booking Modifications</Label>
</div>
<p className='text-sm text-gray-500'>Whether users can edit or cancel their bookings</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())
}
{/* Modification Time Restriction */}
<div className='space-y-2'>
<Label htmlFor='booking_modification_hours_before'>
Modification Time Limit (hours before session)
</Label>
<Input
id='booking_modification_hours_before'
type='number'
min='0.5'
max='48'
step='0.5'
value={settings.booking_modification_hours_before}
onChange={(e) => updateSetting('booking_modification_hours_before', e.target.value)}
disabled={settings.allow_booking_modifications !== 'true'}
/>
<Label htmlFor='allow_weekend_bookings'>Allow Weekend Bookings</Label>
<p className='text-sm text-gray-500'>
{settings.allow_booking_modifications === 'true'
? 'How many hours before a session users can still modify bookings'
: 'Enable booking modifications to configure this setting'}
</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>
<p className='text-sm text-gray-500'>Whether users can book courts on weekends</p>
</div>
</div>
+84 -11
View File
@@ -7,6 +7,17 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog';
import { Switch } from '@/components/ui/switch';
import { Plus, Edit, Trash2, Clock } from 'lucide-react';
import { useToast } from '@/hooks/use-toast';
@@ -30,7 +41,11 @@ export function AdminTimeSlotManagement() {
const [timeSlots, setTimeSlots] = useState<TimeSlot[]>([]);
const [loading, setLoading] = useState(false);
const [showDialog, setShowDialog] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [showWipeDayDialog, setShowWipeDayDialog] = useState(false);
const [editingSlot, setEditingSlot] = useState<TimeSlot | null>(null);
const [slotToDelete, setSlotToDelete] = useState<TimeSlot | null>(null);
const [dayToWipe, setDayToWipe] = useState<number | null>(null);
const [formData, setFormData] = useState({
dayOfWeek: 1, // Default to Monday (Irish standard)
startTime: '',
@@ -115,11 +130,20 @@ export function AdminTimeSlotManagement() {
}
};
const handleDelete = async (id: string) => {
if (!confirm('Are you sure you want to delete this time slot?')) {
return;
}
const openDeleteDialog = (slot: TimeSlot) => {
setSlotToDelete(slot);
setShowDeleteDialog(true);
};
const confirmDeleteSlot = async () => {
if (slotToDelete) {
await handleDelete(slotToDelete.id);
setShowDeleteDialog(false);
setSlotToDelete(null);
}
};
const handleDelete = async (id: string) => {
try {
setLoading(true);
const response = await fetch(`/api/admin/time-slots/${id}`, {
@@ -152,14 +176,23 @@ export function AdminTimeSlotManagement() {
}
};
const handleWipeDay = async (dayOfWeek: number) => {
const dayName = DAYS[dayOfWeek];
if (!confirm(`Are you sure you want to delete ALL time slots for ${dayName}? This action cannot be undone.`)) {
return;
}
const openWipeDayDialog = (dayOfWeek: number) => {
setDayToWipe(dayOfWeek);
setShowWipeDayDialog(true);
};
const confirmWipeDay = async () => {
if (dayToWipe !== null) {
await handleWipeDay(dayToWipe);
setShowWipeDayDialog(false);
setDayToWipe(null);
}
};
const handleWipeDay = async (dayOfWeek: number) => {
try {
setLoading(true);
const dayName = DAYS[dayOfWeek];
const slotsToDelete = timeSlots.filter((slot) => slot.dayOfWeek === dayOfWeek);
// Delete all slots for this day
@@ -322,7 +355,7 @@ export function AdminTimeSlotManagement() {
<Button
size='sm'
variant='outline'
onClick={() => handleWipeDay(jsDayOfWeek)}
onClick={() => openWipeDayDialog(jsDayOfWeek)}
className='text-destructive hover:text-destructive/80 hover:bg-destructive/10'
disabled={loading}
>
@@ -369,7 +402,7 @@ export function AdminTimeSlotManagement() {
<Button
size='sm'
variant='outline'
onClick={() => handleDelete(slot.id)}
onClick={() => openDeleteDialog(slot)}
className='text-destructive hover:text-destructive/80'
>
<Trash2 className='h-4 w-4' />
@@ -389,6 +422,46 @@ export function AdminTimeSlotManagement() {
</div>
)}
</CardContent>
{/* Delete Time Slot Dialog */}
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete this time slot? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={confirmDeleteSlot}
className='bg-destructive hover:bg-destructive/90'
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* Wipe Day Dialog */}
<AlertDialog open={showWipeDayDialog} onOpenChange={setShowWipeDayDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete ALL time slots for{' '}
{dayToWipe !== null ? DAYS[dayToWipe] : 'this day'}? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={confirmWipeDay} className='bg-destructive hover:bg-destructive/90'>
Delete All
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Card>
);
}
+50 -5
View File
@@ -7,6 +7,17 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { useToast } from '@/hooks/use-toast';
@@ -35,7 +46,9 @@ export function AdminUserManagement() {
const [searchTerm, setSearchTerm] = useState('');
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [editingUser, setEditingUser] = useState<User | null>(null);
const [userToDelete, setUserToDelete] = useState<User | null>(null);
const [formData, setFormData] = useState<UserFormData>({
name: '',
surname: '',
@@ -184,12 +197,21 @@ export function AdminUserManagement() {
}
};
const openDeleteDialog = (user: User) => {
setUserToDelete(user);
setIsDeleteDialogOpen(true);
};
const confirmDeleteUser = async () => {
if (userToDelete) {
await handleDeleteUser(userToDelete.id);
setIsDeleteDialogOpen(false);
setUserToDelete(null);
}
};
const handleDeleteUser = async (userId: string) => {
try {
if (!confirm('Are you sure you want to delete this user? This action cannot be undone.')) {
return;
}
const response = await fetch(`/api/admin/users/${userId}`, {
method: 'DELETE',
});
@@ -406,7 +428,7 @@ export function AdminUserManagement() {
<Button
variant='outline'
size='sm'
onClick={() => handleDeleteUser(user.id)}
onClick={() => openDeleteDialog(user)}
className='text-red-600 hover:text-red-700'
>
<Trash2 className='h-4 w-4' />
@@ -498,6 +520,29 @@ export function AdminUserManagement() {
</div>
</DialogContent>
</Dialog>
{/* Delete Confirmation Dialog */}
<AlertDialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete{' '}
{userToDelete ? `${userToDelete.name} ${userToDelete.surname}` : 'this user'}? This action
cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={confirmDeleteUser}
className='bg-destructive hover:bg-destructive/90'
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}