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
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CI

on:
pull_request:
branches:
- main
- dev
push:
branches:
- dev

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build
env:
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
OPS_EMAIL: ${{ secrets.OPS_EMAIL }}
RESEND_FROM_EMAIL: ${{ secrets.RESEND_FROM_EMAIL }}
UPSTASH_REDIS_REST_URL: ${{ secrets.UPSTASH_REDIS_REST_URL }}
UPSTASH_REDIS_REST_TOKEN: ${{ secrets.UPSTASH_REDIS_REST_TOKEN }}
DISPLAY_KEY: ${{ secrets.DISPLAY_KEY }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_SIGNING_SECRET: ${{ secrets.SLACK_SIGNING_SECRET }}
2 changes: 1 addition & 1 deletion app/(dashboard)/administrator/booking-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface BookingModalProps {
export default function BookingModal({ title, onClose, children }: BookingModalProps) {
return (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50">
<div className="bg-[#184073] rounded-2xl shadow-2xl w-full max-w-lg max-h-[90vh] overflow-y-auto p-8 space-y-5">
<div className="bg-[#184073] rounded-2xl shadow-2xl w-full max-w-5xl max-h-[90vh] overflow-y-auto p-8 space-y-5">
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold text-[#f0f6ff]">{title}</h2>
<button onClick={onClose} className="text-[#6a96bb] hover:text-[#f0f6ff] text-lg leading-none transition-colors">✕</button>
Expand Down
70 changes: 68 additions & 2 deletions app/(dashboard)/administrator/one-time-form.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { useState } from 'react'
import { useState, useEffect } from 'react'
import TimePicker from './time-picker'

const STATUSES = [
Expand All @@ -25,6 +25,27 @@ interface Semester {
is_active: boolean
}

interface PendingRequest {
id: string
type: string
purpose: string
status: string
created_at: string
body_id: string
bodies: { name: string } | null
room_request_details: Array<{
start_date: string | null
end_date: string | null
start_time: string | null
end_time: string | null
}> | null
tabling_request_sessions: Array<{
session_date: string | null
start_time: string | null
end_time: string | null
}> | null
}

interface OneTimeSession {
room_name: string
booking_date: string
Expand Down Expand Up @@ -57,6 +78,22 @@ export default function OneTimeForm({ bodies, semesters, onClose, onSuccess }: O
const [sessions, setSessions] = useState<OneTimeSession[]>([emptySession()])
const [saving, setSaving] = useState(false)
const [error, setError] = useState('')
const [pendingRequests, setPendingRequests] = useState<PendingRequest[]>([])

useEffect(() => {
fetch('/api/administrator/requests')
.then(r => r.json())
.then(({ requests }) => {
setPendingRequests(
(requests ?? []).filter((r: PendingRequest) => r.status === 'Pending' && r.type === 'One-Time Room')
)
})
.catch(() => {})
}, [])

const visibleRequests = form.body_id
? pendingRequests.filter(r => r.body_id === form.body_id)
: pendingRequests

const updateSession = (index: number, field: keyof OneTimeSession, value: string) => {
setSessions(prev => prev.map((s, i) => i === index ? { ...s, [field]: value } : s))
Expand Down Expand Up @@ -99,7 +136,8 @@ export default function OneTimeForm({ bodies, semesters, onClose, onSuccess }: O
const labelCls = "block text-xs font-medium text-[#93b8d8] mb-1"

return (
<div className="space-y-3">
<div className="flex gap-8 items-start">
<div className="flex-1 space-y-3">
<div>
<label className={labelCls}>Semester *</label>
{semesters.length === 0 ? (
Expand Down Expand Up @@ -244,6 +282,34 @@ export default function OneTimeForm({ bodies, semesters, onClose, onSuccess }: O
Cancel
</button>
</div>
</div>

<div className="w-96 shrink-0">
<div className="rounded-xl border border-[#1e5080] bg-[#0f2a4a] p-4 h-full">
<p className="text-xs font-semibold text-[#93b8d8] uppercase tracking-wide mb-3">Open Requests</p>
{visibleRequests.length === 0 ? (
<p className="text-xs text-[#6a96bb]">No open requests</p>
) : (
<div className="space-y-2">
{visibleRequests.map(r => (
<div key={r.id} className="rounded-lg bg-[#0a1f38] border border-[#1e5080]/60 px-3 py-2.5">
<div className="flex items-baseline justify-between gap-2 mb-0.5">
<span className="text-sm font-semibold text-[#f0f6ff]">{r.bodies?.name ?? '—'}</span>
<span className="text-xs text-[#6a96bb] shrink-0">{new Date(r.created_at).toLocaleDateString()}</span>
</div>
<p className="text-xs text-[#93b8d8] mb-1.5">{r.purpose}</p>
{(r.room_request_details ?? []).map((d, i) => (
<div key={i} className="flex gap-3 text-xs text-[#6a96bb]">
<span>{d.start_date ?? '—'}</span>
<span>{d.start_time?.slice(0, 5) ?? '—'} – {d.end_time?.slice(0, 5) ?? '—'}</span>
</div>
))}
</div>
))}
</div>
)}
</div>
</div>
</div>
)
}
70 changes: 68 additions & 2 deletions app/(dashboard)/administrator/tabling-form.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { useState } from 'react'
import { useState, useEffect } from 'react'
import TimePicker from './time-picker'

const STATUSES = [
Expand Down Expand Up @@ -33,6 +33,27 @@ interface Session {
status: string
}

interface PendingRequest {
id: string
type: string
purpose: string
status: string
created_at: string
body_id: string
bodies: { name: string } | null
room_request_details: Array<{
start_date: string | null
end_date: string | null
start_time: string | null
end_time: string | null
}> | null
tabling_request_sessions: Array<{
session_date: string | null
start_time: string | null
end_time: string | null
}> | null
}

interface TablingFormProps {
bodies: Body[]
semesters: Semester[]
Expand Down Expand Up @@ -62,6 +83,22 @@ export default function TablingForm({ bodies, semesters, onClose, onSuccess }: T
const [sessions, setSessions] = useState<Session[]>([emptySession()])
const [saving, setSaving] = useState(false)
const [error, setError] = useState('')
const [pendingRequests, setPendingRequests] = useState<PendingRequest[]>([])

useEffect(() => {
fetch('/api/administrator/requests')
.then(r => r.json())
.then(({ requests }) => {
setPendingRequests(
(requests ?? []).filter((r: PendingRequest) => r.status === 'Pending' && r.type === 'Tabling')
)
})
.catch(() => {})
}, [])

const visibleRequests = form.body_id
? pendingRequests.filter(r => r.body_id === form.body_id)
: pendingRequests

const updateSession = (index: number, field: keyof Session, value: string) => {
setSessions(prev => prev.map((s, i) => i === index ? { ...s, [field]: value } : s))
Expand Down Expand Up @@ -109,7 +146,8 @@ export default function TablingForm({ bodies, semesters, onClose, onSuccess }: T
}

return (
<div className="space-y-4">
<div className="flex gap-8 items-start">
<div className="flex-1 space-y-4">
<div>
<label className={labelCls}>Semester *</label>
{semesters.length === 0 ? (
Expand Down Expand Up @@ -255,6 +293,34 @@ export default function TablingForm({ bodies, semesters, onClose, onSuccess }: T
Cancel
</button>
</div>
</div>

<div className="w-96 shrink-0">
<div className="rounded-xl border border-[#1e5080] bg-[#0f2a4a] p-4 h-full">
<p className="text-xs font-semibold text-[#93b8d8] uppercase tracking-wide mb-3">Open Requests</p>
{visibleRequests.length === 0 ? (
<p className="text-xs text-[#6a96bb]">No open requests</p>
) : (
<div className="space-y-2">
{visibleRequests.map(r => (
<div key={r.id} className="rounded-lg bg-[#0a1f38] border border-[#1e5080]/60 px-3 py-2.5">
<div className="flex items-baseline justify-between gap-2 mb-0.5">
<span className="text-sm font-semibold text-[#f0f6ff]">{r.bodies?.name ?? '—'}</span>
<span className="text-xs text-[#6a96bb] shrink-0">{new Date(r.created_at).toLocaleDateString()}</span>
</div>
<p className="text-xs text-[#93b8d8] mb-1.5">{r.purpose}</p>
{(r.tabling_request_sessions ?? []).map((s, i) => (
<div key={i} className="flex gap-3 text-xs text-[#6a96bb]">
<span>{s.session_date ?? '—'}</span>
<span>{s.start_time?.slice(0, 5) ?? '—'} – {s.end_time?.slice(0, 5) ?? '—'}</span>
</div>
))}
</div>
))}
</div>
)}
</div>
</div>
</div>
)
}
70 changes: 68 additions & 2 deletions app/(dashboard)/administrator/weekly-form.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { useState } from 'react'
import { useState, useEffect } from 'react'
import TimePicker from './time-picker'

const STATUSES = [
Expand All @@ -25,6 +25,27 @@ interface Semester {
is_active: boolean
}

interface PendingRequest {
id: string
type: string
purpose: string
status: string
created_at: string
body_id: string
bodies: { name: string } | null
room_request_details: Array<{
start_date: string | null
end_date: string | null
start_time: string | null
end_time: string | null
}> | null
tabling_request_sessions: Array<{
session_date: string | null
start_time: string | null
end_time: string | null
}> | null
}

interface WeeklyFormProps {
bodies: Body[]
semesters: Semester[]
Expand All @@ -51,6 +72,22 @@ export default function WeeklyForm({ bodies, semesters, onClose, onSuccess }: We
})
const [saving, setSaving] = useState(false)
const [error, setError] = useState('')
const [pendingRequests, setPendingRequests] = useState<PendingRequest[]>([])

useEffect(() => {
fetch('/api/administrator/requests')
.then(r => r.json())
.then(({ requests }) => {
setPendingRequests(
(requests ?? []).filter((r: PendingRequest) => r.status === 'Pending' && r.type === 'Weekly Room')
)
})
.catch(() => {})
}, [])

const visibleRequests = form.body_id
? pendingRequests.filter(r => r.body_id === form.body_id)
: pendingRequests

const handleSubmit = async () => {
if (!semesterId) {
Expand Down Expand Up @@ -85,7 +122,8 @@ export default function WeeklyForm({ bodies, semesters, onClose, onSuccess }: We
}

return (
<div className="space-y-3">
<div className="flex gap-8 items-start">
<div className="flex-1 space-y-3">
<div>
<label className={labelCls}>Semester *</label>
{semesters.length === 0 ? (
Expand Down Expand Up @@ -213,6 +251,34 @@ export default function WeeklyForm({ bodies, semesters, onClose, onSuccess }: We
Cancel
</button>
</div>
</div>

<div className="w-96 shrink-0">
<div className="rounded-xl border border-[#1e5080] bg-[#0f2a4a] p-4 h-full">
<p className="text-xs font-semibold text-[#93b8d8] uppercase tracking-wide mb-3">Open Requests</p>
{visibleRequests.length === 0 ? (
<p className="text-xs text-[#6a96bb]">No open requests</p>
) : (
<div className="space-y-2">
{visibleRequests.map(r => (
<div key={r.id} className="rounded-lg bg-[#0a1f38] border border-[#1e5080]/60 px-3 py-2.5">
<div className="flex items-baseline justify-between gap-2 mb-0.5">
<span className="text-sm font-semibold text-[#f0f6ff]">{r.bodies?.name ?? '—'}</span>
<span className="text-xs text-[#6a96bb] shrink-0">{new Date(r.created_at).toLocaleDateString()}</span>
</div>
<p className="text-xs text-[#93b8d8] mb-1.5">{r.purpose}</p>
{(r.room_request_details ?? []).map((d, i) => (
<div key={i} className="text-xs text-[#6a96bb] space-y-0.5">
<div>{d.start_date ?? '—'} – {d.end_date ?? '—'}</div>
<div>{d.start_time?.slice(0, 5) ?? '—'} – {d.end_time?.slice(0, 5) ?? '—'}</div>
</div>
))}
</div>
))}
</div>
)}
</div>
</div>
</div>
)
}
2 changes: 1 addition & 1 deletion app/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ export default function DashboardLayout({
<span className="text-[#c8102e] font-bold text-xl tracking-tight">Chambers</span>
</div>
<p className="text-slate-500 text-xs mt-0.5">NU Student Gov. Association</p>
<p className="text-slate-600 text-xs mt-1">v1.11.9</p>
<p className="text-slate-600 text-xs mt-1">v1.12.0</p>
{userName && (
<div className="flex items-start justify-between mt-2">
<p className="text-slate-500 text-xs italic">{getGreeting()},<br />{userName}</p>
Expand Down
Loading
Loading