Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 73 additions & 4 deletions app/(authentication)/admin/superguide/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import { Container } from '@/components/Container'
import { Button } from '@/components/Button'
import React from 'react'
import React, { useState, useEffect } from 'react'
import Link from 'next/link'
import BroadcastFormModal from '@/components/BroadcastFormModal'

const openNewYearForm = async () => {
// Show loading message
Expand Down Expand Up @@ -108,13 +109,71 @@ const openNewYearForm = async () => {

const adminHelp = () => {
const currentYear = new Date().getFullYear();
const [showBroadcastModal, setShowBroadcastModal] = useState(false);
const [userInfo, setUserInfo] = useState<{userId: number, userRoles: string[]} | null>(null);

// Get user info from cookies on component mount
useEffect(() => {
const getUserInfo = () => {
// Get user info from cookies (this should match your authentication system)
const userIdCookie = document.cookie
.split('; ')
.find(row => row.startsWith('userId='))
?.split('=')[1];

const userRolesCookie = document.cookie
.split('; ')
.find(row => row.startsWith('userRoles='))
?.split('=')[1];

if (userIdCookie && userRolesCookie) {
try {
const roles = JSON.parse(decodeURIComponent(userRolesCookie));
setUserInfo({
userId: parseInt(userIdCookie),
userRoles: roles
});
} catch (error) {
console.error('Failed to parse user info from cookies:', error);
}
}
};

getUserInfo();
}, []);

return (
<>
<h1>Admin Help</h1>
<Container>
<Button className='w-[200px]' onClick={() => openNewYearForm()}>Open for Year of {currentYear}</Button>
<Button className='w-[200px]'><Link href="/signup" className='text-white'>Sign Up New User</Link></Button>
<Button className='w-[200px]'><Link href="/create" className='text-white'>Create New Library</Link></Button>
<div className="mb-6 space-y-4">
<div className="flex flex-wrap gap-4">
<Button className='w-[200px]' onClick={() => openNewYearForm()}>Open for Year of {currentYear}</Button>
<Button className='w-[200px]'><Link href="/signup" className='text-white'>Sign Up New User</Link></Button>
<Button className='w-[200px]'><Link href="/create" className='text-white'>Create New Library</Link></Button>

{/* New Broadcast Button - Only show for super admins */}
{userInfo && userInfo.userRoles.includes('1') && (
<Button
className='w-[250px] bg-blue-600 hover:bg-blue-700'
onClick={() => setShowBroadcastModal(true)}
>
📧 Open Forms & Broadcast
</Button>
)}
</div>

{/* Info box for broadcast feature */}
{userInfo && userInfo.userRoles.includes('1') && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<h3 className="font-semibold text-blue-800 mb-2">📧 Form Broadcasting System</h3>
<p className="text-sm text-blue-700">
Use "Open Forms & Broadcast" to create a new form session, open all library forms for editing (62-day period),
and automatically send notification emails to all CEAL members.
</p>
</div>
)}
</div>
<ul>
<li><a href="#useradministration">User Adminstration</a></li>
<li><a href="#libraryadministration">Library Administration</a></li>
Expand Down Expand Up @@ -318,6 +377,16 @@ const adminHelp = () => {
<li>Find the file in the list, to the right of the file name you will see its URL.&nbsp; Copy and paste this as the link to the file.</li>
</ol>
</Container>

{/* Broadcast Modal */}
{userInfo && (
<BroadcastFormModal
isOpen={showBroadcastModal}
onClose={() => setShowBroadcastModal(false)}
userId={userInfo.userId}
userRoles={userInfo.userRoles}
/>
)}
</>
)
}
Expand Down
1 change: 1 addition & 0 deletions app/api/admin/broadcast/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export async function POST(request: NextRequest) {
);
}


// Check if RESEND_API_KEY is available
if (!process.env.RESEND_API_KEY) {
return NextResponse.json(
Expand Down
183 changes: 183 additions & 0 deletions app/api/admin/form-session/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
import { logUserAction } from '@/lib/auditLogger';

const prisma = new PrismaClient();

export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const year = searchParams.get('year');

let whereClause;

if (year) {
whereClause = { year: parseInt(year) };
} else {
// Get most recent year with open forms
const currentYear = new Date().getFullYear();
whereClause = { year: currentYear };
}

// Get Library_Year records for the specified year
const libraryYears = await prisma.library_Year.findMany({
where: whereClause,
include: {
Library: {
select: {
id: true,
library_name: true
}
}
},
orderBy: {
updated_at: 'desc'
}
});

if (libraryYears.length === 0) {
return NextResponse.json({
session: null,
isOpen: false,
message: year ? `No libraries found for year ${year}` : 'No library records found'
});
}

const openLibraries = libraryYears.filter(ly => ly.is_open_for_editing);
const isOpen = openLibraries.length > 0;
const targetYear = libraryYears[0].year;

return NextResponse.json({
session: {
year: targetYear,
totalLibraries: libraryYears.length,
openLibraries: openLibraries.length,
closedLibraries: libraryYears.length - openLibraries.length,
lastUpdated: libraryYears[0].updated_at,
libraries: libraryYears.map(ly => ({
id: ly.id,
libraryId: ly.library,
libraryName: ly.Library?.library_name,
isOpen: ly.is_open_for_editing,
isActive: ly.is_active,
adminNotes: ly.admin_notes
}))
},
isOpen: isOpen,
status: {
totalLibraries: libraryYears.length,
openForEditing: openLibraries.length,
percentage: Math.round((openLibraries.length / libraryYears.length) * 100)
}
});

} catch (error) {
console.error('Form session status error:', error);
return NextResponse.json(
{ error: 'Failed to retrieve form session status' },
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

export async function PATCH(request: NextRequest) {
try {
const { year, action, userId, userRoles } = await request.json();

// Verify user is super admin
if (!userRoles || !userRoles.includes('1')) {
return NextResponse.json(
{ error: 'Unauthorized: Super admin access required' },
{ status: 403 }
);
}

if (!year || !action) {
return NextResponse.json(
{ error: 'Missing required fields: year, action' },
{ status: 400 }
);
}

// Check if Library_Year records exist for this year
const existingLibraries = await prisma.library_Year.findMany({
where: { year: year }
});

if (existingLibraries.length === 0) {
return NextResponse.json(
{ error: `No library records found for year ${year}` },
{ status: 404 }
);
}

let libraryUpdateData = {};
let adminNote = '';

switch (action) {
case 'close':
libraryUpdateData = {
is_open_for_editing: false,
updated_at: new Date()
};
adminNote = `Forms closed by admin on ${new Date().toISOString()}`;
break;

case 'reopen':
libraryUpdateData = {
is_open_for_editing: true,
updated_at: new Date()
};
adminNote = `Forms reopened by admin on ${new Date().toISOString()}`;
break;

default:
return NextResponse.json(
{ error: 'Invalid action. Valid actions: close, reopen' },
{ status: 400 }
);
}

// Update all library year records for this year
const libraryUpdateResult = await prisma.library_Year.updateMany({
where: { year: year },
data: libraryUpdateData
});

// Log the action
await logUserAction(
'UPDATE',
'Library_Year',
`year_${year}`,
null,
{
year: year,
action: action,
libraries_affected: libraryUpdateResult.count,
admin_note: adminNote
},
true,
undefined,
request
);

return NextResponse.json({
success: true,
year: year,
action: action,
librariesUpdated: libraryUpdateResult.count,
message: `Forms ${action} completed successfully for ${libraryUpdateResult.count} libraries in year ${year}`
});

} catch (error) {
console.error('Form session update error:', error);
return NextResponse.json(
{ error: 'Failed to update form session' },
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}
Loading