diff --git a/app/(dashboard)/administrator/archive-tab.tsx b/app/(dashboard)/administrator/archive-tab.tsx index 3e0a2b6..3d397c6 100644 --- a/app/(dashboard)/administrator/archive-tab.tsx +++ b/app/(dashboard)/administrator/archive-tab.tsx @@ -157,6 +157,13 @@ function AdminRoleBadge({ role }: { role: string | null | undefined }) { ) } + if (role === 'Information Manager') { + return ( + + Info Manager + + ) + } return null } diff --git a/app/(dashboard)/administrator/audit-tab.tsx b/app/(dashboard)/administrator/audit-tab.tsx index abc6b2c..1e943a5 100644 --- a/app/(dashboard)/administrator/audit-tab.tsx +++ b/app/(dashboard)/administrator/audit-tab.tsx @@ -64,6 +64,13 @@ function AdminRoleBadge({ role }: { role: string | null | undefined }) { ) } + if (role === 'Information Manager') { + return ( + + Info Manager + + ) + } return null } diff --git a/app/(dashboard)/administrator/booking-settings-tab.tsx b/app/(dashboard)/administrator/booking-settings-tab.tsx index 2e475e8..f7c1e68 100644 --- a/app/(dashboard)/administrator/booking-settings-tab.tsx +++ b/app/(dashboard)/administrator/booking-settings-tab.tsx @@ -88,7 +88,11 @@ export default function BookingSettingsTab() { process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! ) supabase.auth.getUser().then(({ data: { user } }) => { - if (user?.app_metadata?.admin_role === 'Vice President of Operational Affairs') { + if ( + user?.app_metadata?.admin_role === 'Vice President of Operational Affairs' || + user?.app_metadata?.admin_role === 'Executive Vice President' || + user?.app_metadata?.admin_role === 'Information Manager' + ) { setIsVP(true) } }) diff --git a/app/(dashboard)/administrator/bookings-tab.tsx b/app/(dashboard)/administrator/bookings-tab.tsx index 89b0a52..6c1dc2f 100644 --- a/app/(dashboard)/administrator/bookings-tab.tsx +++ b/app/(dashboard)/administrator/bookings-tab.tsx @@ -147,6 +147,13 @@ function AdminRoleBadge({ role }: { role: string | null | undefined }) { ) } + if (role === 'Information Manager') { + return ( + + Info Manager + + ) + } return null } @@ -157,6 +164,7 @@ export default function BookingsTab() { const [tabling, setTabling] = useState([]) const [bodies, setBodies] = useState([]) const [semesters, setSemesters] = useState([]) + const [showAll, setShowAll] = useState(false) const [loading, setLoading] = useState(true) const [showModal, setShowModal] = useState(false) const [editingBooking, setEditingBooking] = useState(null) @@ -167,8 +175,8 @@ export default function BookingsTab() { sessions: { id: string; label: string }[] } | null>(null) - const fetchBookings = async () => { - const res = await fetch('/api/administrator/bookings') + const fetchBookings = async (all = false) => { + const res = await fetch(`/api/administrator/bookings${all ? '?all=true' : ''}`) const data = await res.json() setOneTime(data.oneTime || []) setWeekly(data.weekly || []) @@ -211,10 +219,10 @@ export default function BookingsTab() { } useEffect(() => { - fetchBookings() + fetchBookings(showAll) fetchBodies() fetchSemesters() - }, []) + }, [showAll]) const sortedWeekly = loading ? [] : [...weekly].sort((a, b) => { const wa = a.weekly_room_bookings?.[0] @@ -245,8 +253,17 @@ export default function BookingsTab() { ))} - {/* Create button */} -
+ {/* Create button + all-semesters toggle */} +
+ + ))} +
+ {bookings.length === 0 ? ( +
No space bookings for this period.
+ ) : ( +
+ + + + + + + + + + + + + + {bookings.map(b => ( + + + + + + + + + + ))} + +
SpaceTitleCreatorStartEndAttendees
{b.spaces?.name ?? '—'}{b.title}{b.creator_name ?? '—'}{formatDateTime(b.start_time)}{formatDateTime(b.end_time)}{(b.attendee_ids ?? []).length + 1} + +
+
+ )} ) } diff --git a/app/(dashboard)/administrator/tabling-form.tsx b/app/(dashboard)/administrator/tabling-form.tsx index 3557a7e..c29b129 100644 --- a/app/(dashboard)/administrator/tabling-form.tsx +++ b/app/(dashboard)/administrator/tabling-form.tsx @@ -12,6 +12,9 @@ const STATUSES = [ 'Pending Cancellation', 'Cancelled', 'Virtual', + 'Missed', + 'Repurposed', + 'Tentative', ] interface Body { diff --git a/app/(dashboard)/administrator/users-tab.tsx b/app/(dashboard)/administrator/users-tab.tsx index c1405be..d14b178 100644 --- a/app/(dashboard)/administrator/users-tab.tsx +++ b/app/(dashboard)/administrator/users-tab.tsx @@ -32,6 +32,7 @@ const ADMIN_ROLES = [ 'Comptroller', 'Digital Innovation Manager', 'Digital Innovation Project Member', + 'Information Manager', ] const IEMS_ROLES = [ @@ -43,6 +44,7 @@ const ROLE_EDITORS = [ 'Executive Vice President', 'Vice President of Operational Affairs', 'Digital Innovation Manager', + 'Information Manager', ] interface Membership { diff --git a/app/(dashboard)/administrator/weekly-form.tsx b/app/(dashboard)/administrator/weekly-form.tsx index 7d9ea6a..47ff5b5 100644 --- a/app/(dashboard)/administrator/weekly-form.tsx +++ b/app/(dashboard)/administrator/weekly-form.tsx @@ -12,6 +12,9 @@ const STATUSES = [ 'Pending Cancellation', 'Cancelled', 'Virtual', + 'Missed', + 'Repurposed', + 'Tentative', ] interface Body { diff --git a/app/(dashboard)/layout.tsx b/app/(dashboard)/layout.tsx index d861ece..7220e88 100644 --- a/app/(dashboard)/layout.tsx +++ b/app/(dashboard)/layout.tsx @@ -233,7 +233,7 @@ export default function DashboardLayout({ Chambers

NU Student Gov. Association

-

v1.12.1

+

v1.12.2

{userName && (

{getGreeting()},
{userName}

diff --git a/app/api/administrator/bookings/route.ts b/app/api/administrator/bookings/route.ts index dfcc785..d7fccfc 100644 --- a/app/api/administrator/bookings/route.ts +++ b/app/api/administrator/bookings/route.ts @@ -8,7 +8,7 @@ const adminSupabase = createAdminClient( process.env.SUPABASE_SERVICE_ROLE_KEY! ) -export async function GET() { +export async function GET(request: Request) { const supabase = await createClient() const { data: { user } } = await supabase.auth.getUser() @@ -19,62 +19,70 @@ export async function GET() { const rateLimitRes = await checkRateLimit(user.id) if (rateLimitRes) return rateLimitRes - // Only show bookings for the active semester + const { searchParams } = new URL(request.url) + const all = searchParams.get('all') === 'true' + const { data: activeSemester } = await supabase .from('semesters') .select('id, name') .eq('is_active', true) .single() - if (!activeSemester) { + if (!all && !activeSemester) { return NextResponse.json({ oneTime: [], weekly: [], tabling: [], activeSemester: null }) } + const semesterId = (!all && activeSemester) ? activeSemester.id : null + + let oneTimeQ = supabase + .from('bookings') + .select(` + id, purpose, body_id, is_event, hidden, + bodies(name), + creator_role, + one_time_room_bookings(id, room_name, booking_date, start_time, end_time, status, reservation_code) + `) + .eq('type', 'One-Time Room') + .order('created_at', { ascending: false }) + if (semesterId) oneTimeQ = oneTimeQ.eq('semester_id', semesterId) + + let weeklyQ = supabase + .from('bookings') + .select(` + id, purpose, body_id, is_event, hidden, + bodies(name), + creator_role, + weekly_room_bookings(id, room_name, start_date, end_date, start_time, end_time, status, reservation_code, + weekly_room_occurrences(id, occurrence_date, room_name, start_time, end_time, status, reservation_code, senate_type) + ) + `) + .eq('type', 'Weekly Room') + .order('created_at', { ascending: false }) + if (semesterId) weeklyQ = weeklyQ.eq('semester_id', semesterId) + + let tablingQ = supabase + .from('bookings') + .select(` + id, purpose, body_id, is_event, hidden, + bodies(name), + creator_role, + tabling_bookings(id, reservation_code, + tabling_sessions(id, location, session_date, start_time, end_time, status, reservation_code) + ) + `) + .eq('type', 'Tabling') + .order('created_at', { ascending: false }) + if (semesterId) tablingQ = tablingQ.eq('semester_id', semesterId) + const [{ data: oneTime }, { data: weekly }, { data: tabling }] = await Promise.all([ - supabase - .from('bookings') - .select(` - id, purpose, body_id, is_event, hidden, - bodies(name), - creator_role, - one_time_room_bookings(id, room_name, booking_date, start_time, end_time, status, reservation_code) - `) - .eq('type', 'One-Time Room') - .eq('semester_id', activeSemester.id) - .order('created_at', { ascending: false }), - supabase - .from('bookings') - .select(` - id, purpose, body_id, is_event, hidden, - bodies(name), - creator_role, - weekly_room_bookings(id, room_name, start_date, end_date, start_time, end_time, status, reservation_code, - weekly_room_occurrences(id, occurrence_date, room_name, start_time, end_time, status, reservation_code, senate_type) - ) - `) - .eq('type', 'Weekly Room') - .eq('semester_id', activeSemester.id) - .order('created_at', { ascending: false }), - supabase - .from('bookings') - .select(` - id, purpose, body_id, is_event, hidden, - bodies(name), - creator_role, - tabling_bookings(id, reservation_code, - tabling_sessions(id, location, session_date, start_time, end_time, status, reservation_code) - ) - `) - .eq('type', 'Tabling') - .eq('semester_id', activeSemester.id) - .order('created_at', { ascending: false }), + oneTimeQ, weeklyQ, tablingQ, ]) return NextResponse.json({ oneTime: oneTime || [], weekly: weekly || [], tabling: tabling || [], - activeSemester, + activeSemester: activeSemester ?? null, }) } diff --git a/app/api/administrator/semesters/route.ts b/app/api/administrator/semesters/route.ts index 57e20da..d58a2dc 100644 --- a/app/api/administrator/semesters/route.ts +++ b/app/api/administrator/semesters/route.ts @@ -92,7 +92,12 @@ export async function DELETE(request: Request) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } - if (user.app_metadata?.admin_role !== 'Vice President of Operational Affairs') { + const semesterManagers = [ + 'Vice President of Operational Affairs', + 'Executive Vice President', + 'Information Manager', + ] + if (!semesterManagers.includes(user.app_metadata?.admin_role)) { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) } diff --git a/app/api/administrator/users/route.ts b/app/api/administrator/users/route.ts index f679426..9ae2a8e 100644 --- a/app/api/administrator/users/route.ts +++ b/app/api/administrator/users/route.ts @@ -14,6 +14,7 @@ const ROLE_EDITORS = [ 'Executive Vice President', 'Vice President of Operational Affairs', 'Digital Innovation Manager', + 'Information Manager', ] export async function GET() { diff --git a/app/api/spaces/bookings/route.ts b/app/api/spaces/bookings/route.ts index 40f2a02..ae25a5d 100644 --- a/app/api/spaces/bookings/route.ts +++ b/app/api/spaces/bookings/route.ts @@ -58,7 +58,8 @@ export async function GET(request: Request) { return NextResponse.json({ error: 'space_id and week_start required' }, { status: 400 }) } - const weekEnd = new Date(new Date(weekStart).getTime() + 7 * 24 * 60 * 60 * 1000).toISOString() + const weekEndParam = searchParams.get('week_end') + const weekEnd = weekEndParam ?? new Date(new Date(weekStart).getTime() + 7 * 24 * 60 * 60 * 1000).toISOString() // Fetch bookings and blackouts in parallel (no join — creator FK points to auth.users which PostgREST can't reach) const [ diff --git a/lib/email-preferences.ts b/lib/email-preferences.ts index 42b02a3..e992bdc 100644 --- a/lib/email-preferences.ts +++ b/lib/email-preferences.ts @@ -21,6 +21,7 @@ export const EMAIL_PREF_ADMIN_ROLES = [ 'Executive Vice President', 'Vice President of Operational Affairs', 'Comptroller', + 'Information Manager', ] export const EMAIL_PREF_IEMS_ROLES = [ diff --git a/package-lock.json b/package-lock.json index 9cc594b..6522b49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "chambers", - "version": "1.12.1", + "version": "1.12.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "chambers", - "version": "1.12.1", + "version": "1.12.2", "dependencies": { "@supabase/ssr": "^0.9.0", "@supabase/supabase-js": "^2.99.1", diff --git a/package.json b/package.json index cdd75a4..7630d4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chambers", - "version": "1.12.1", + "version": "1.12.2", "private": true, "scripts": { "dev": "next dev",