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.
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { courtBlocks, courts, users, announcements } from '@/lib/db/schema';
|
||||
import { eq, gte, asc } from 'drizzle-orm';
|
||||
import { getSession } from '@/lib/session';
|
||||
import { logActivity, ACTIONS, ENTITY_TYPES } from '@/lib/activity-logger';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session || session.role !== 'admin') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const includeExpired = searchParams.get('includeExpired') === 'true';
|
||||
|
||||
// Get today's date
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
|
||||
// Fetch blocks with court and creator info
|
||||
let query = db
|
||||
.select({
|
||||
id: courtBlocks.id,
|
||||
courtId: courtBlocks.courtId,
|
||||
date: courtBlocks.date,
|
||||
startTime: courtBlocks.startTime,
|
||||
endTime: courtBlocks.endTime,
|
||||
reason: courtBlocks.reason,
|
||||
createdBy: courtBlocks.createdBy,
|
||||
createdAt: courtBlocks.createdAt,
|
||||
court: {
|
||||
id: courts.id,
|
||||
name: courts.name,
|
||||
},
|
||||
creator: {
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
surname: users.surname,
|
||||
},
|
||||
})
|
||||
.from(courtBlocks)
|
||||
.leftJoin(courts, eq(courtBlocks.courtId, courts.id))
|
||||
.innerJoin(users, eq(courtBlocks.createdBy, users.id));
|
||||
|
||||
const rawBlocks = includeExpired
|
||||
? await query.orderBy(asc(courtBlocks.date), asc(courtBlocks.startTime))
|
||||
: await query
|
||||
.where(gte(courtBlocks.date, today))
|
||||
.orderBy(asc(courtBlocks.date), asc(courtBlocks.startTime));
|
||||
|
||||
// Transform to flat structure for frontend
|
||||
const blocks = rawBlocks.map((block) => ({
|
||||
id: block.id,
|
||||
courtId: block.courtId,
|
||||
courtName: block.court?.name || null,
|
||||
date: block.date,
|
||||
startTime: block.startTime,
|
||||
endTime: block.endTime,
|
||||
reason: block.reason,
|
||||
createdBy: block.createdBy,
|
||||
creatorName: block.creator ? `${block.creator.name} ${block.creator.surname}` : 'Unknown',
|
||||
createdAt: block.createdAt,
|
||||
}));
|
||||
|
||||
return NextResponse.json({ blocks });
|
||||
} catch (error) {
|
||||
console.error('Error fetching blocks:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session || session.role !== 'admin') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { courtId, date, startTime, endTime, reason, createAnnouncement } = await request.json();
|
||||
|
||||
if (!date || !startTime || !endTime || !reason) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing required fields: date, startTime, endTime, reason' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Validate date is not in the past
|
||||
const blockDate = new Date(date);
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
if (blockDate < today) {
|
||||
return NextResponse.json({ error: 'Cannot create blocks for past dates' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get court name if courtId is provided
|
||||
let courtName = 'All Courts';
|
||||
if (courtId) {
|
||||
const court = await db.select().from(courts).where(eq(courts.id, courtId)).limit(1);
|
||||
if (court.length === 0) {
|
||||
return NextResponse.json({ error: 'Court not found' }, { status: 400 });
|
||||
}
|
||||
courtName = court[0].name;
|
||||
}
|
||||
|
||||
// Create the block
|
||||
const blockId = crypto.randomUUID();
|
||||
const [newBlock] = await db
|
||||
.insert(courtBlocks)
|
||||
.values({
|
||||
id: blockId,
|
||||
courtId: courtId || null, // null means all courts
|
||||
date,
|
||||
startTime,
|
||||
endTime,
|
||||
reason,
|
||||
createdBy: session.userId,
|
||||
createdAt: new Date(),
|
||||
})
|
||||
.returning();
|
||||
|
||||
// Log activity
|
||||
await logActivity({
|
||||
userId: session.userId,
|
||||
action: ACTIONS.BLOCK_CREATE,
|
||||
entityType: ENTITY_TYPES.COURT_BLOCK,
|
||||
entityId: blockId,
|
||||
details: {
|
||||
courtId: courtId || 'all',
|
||||
date,
|
||||
startTime,
|
||||
endTime,
|
||||
reason,
|
||||
},
|
||||
});
|
||||
|
||||
// Optionally create an announcement that expires when the block ends
|
||||
let announcementCreated = false;
|
||||
if (createAnnouncement) {
|
||||
const formattedDate = new Date(date).toLocaleDateString('en-IE', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
});
|
||||
|
||||
// Set expiry to end of block day at the end time
|
||||
const expiryDate = new Date(date);
|
||||
const [endHour] = endTime.split(':').map(Number);
|
||||
expiryDate.setHours(endHour, 0, 0, 0);
|
||||
|
||||
await db.insert(announcements).values({
|
||||
id: crypto.randomUUID(),
|
||||
title: `${reason} - ${formattedDate}`,
|
||||
content: `${courtName} will be unavailable on ${formattedDate} from ${startTime} to ${endTime} due to: ${reason}`,
|
||||
priority: 'high',
|
||||
expiresAt: expiryDate,
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
announcementCreated = true;
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
block: newBlock,
|
||||
announcementCreated,
|
||||
message: 'Block created successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating block:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user