No upcoming space bookings.
- }
-
return (
-
Showing current week. Force-cancelling sends email to creator and all attendees.
-
-
-
-
- | Space |
- Title |
- Creator |
- Start |
- End |
- Attendees |
- |
-
-
-
- {bookings.map(b => (
-
- | {b.spaces?.name ?? '—'} |
- {b.title} |
- {b.creator_name ?? '—'} |
- {formatDateTime(b.start_time)} |
- {formatDateTime(b.end_time)} |
- {(b.attendee_ids ?? []).length + 1} |
-
-
- |
-
- ))}
-
-
+
+
{RANGE_DESCRIPTIONS[range]} Force-cancelling sends email to creator and all attendees.
+
+ {(['week', 'month', 'semester', 'all'] as BookingRange[]).map(r => (
+
+ ))}
+
+ {bookings.length === 0 ? (
+
No space bookings for this period.
+ ) : (
+
+
+
+
+ | Space |
+ Title |
+ Creator |
+ Start |
+ End |
+ Attendees |
+ |
+
+
+
+ {bookings.map(b => (
+
+ | {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
-
{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",