Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 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
26 changes: 13 additions & 13 deletions app/components/ClassContextSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ const selectedClassToken = useCookie<string | null>('selected-class-token', {
const { data: session } = await authClient.useSession(useFetch)

const { data: classes, status: classesStatus } = await useFetch<ClassOption[]>(
'/api/universal-admin/classes',
'/api/admin/classes',
{
key: 'universal-admin-classes',
key: `admin-classes-${session.value?.user?.id ?? 'anonymous'}`,
default: () => [],
}
)
Expand All @@ -27,11 +27,11 @@ function getRouteClassToken() {
}

function getCurrentContext() {
if (route.path === '/create-class') {
if (route.path === '/coach/create-class') {
return 'create-class'
}

if (route.path.startsWith('/admin')) {
if (route.path.startsWith('/coach')) {
const classToken = getRouteClassToken() || selectedClassToken.value
return classToken ? `class:${classToken}` : 'admin'
}
Expand All @@ -43,7 +43,7 @@ const selectedContext = ref(getCurrentContext())
const isRedirecting = ref(false)
const canAccessUniversalAdmin = computed(() => session.value?.user?.role === 'admin')
const canManageClasses = computed(
() => session.value?.user?.role === 'admin' || session.value?.user?.role === 'poster'
() => session.value?.user?.role === 'admin' || session.value?.user?.role === 'coach'
)

watch(
Expand Down Expand Up @@ -78,22 +78,22 @@ watch(
if (
!canAccessUniversalAdmin.value &&
classes.value.length === 0 &&
route.path !== '/create-class'
route.path !== '/coach/create-class'
) {
selectedClassToken.value = null
await router.replace('/create-class')
await router.replace('/coach/create-class')
return
}

if (route.path.startsWith('/admin')) {
if (route.path.startsWith('/coach') && route.path !== '/coach/create-class') {
const classToken = getRouteClassToken() || selectedClassToken.value
const classExists = classes.value.some((classroom) => classroom.joinToken === classToken)

if (!classToken || !classExists) {
selectedClassToken.value = null

if (canAccessUniversalAdmin.value) {
await router.replace('/universal-admin')
await router.replace('/admin')
return
}

Expand All @@ -102,7 +102,7 @@ watch(
if (firstClass) {
selectedClassToken.value = firstClass.joinToken
await router.replace({
path: '/admin',
path: '/coach',
query: { class: firstClass.joinToken },
})
}
Expand All @@ -123,19 +123,19 @@ async function changeContext() {
}

selectedClassToken.value = null
await router.push('/universal-admin')
await router.push('/admin')
return
}

if (selectedContext.value === 'create-class') {
await router.push('/create-class')
await router.push('/coach/create-class')
return
}

const classToken = selectedContext.value.replace(/^class:/, '')
selectedClassToken.value = classToken
await router.push({
path: '/admin',
path: '/coach',
query: { class: classToken },
})
}
Expand Down
90 changes: 75 additions & 15 deletions app/composables/useAdmin.ts → app/composables/useCoach.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
// composables/useAdmin.ts
// Place this at: app/composables/useAdmin.ts
// composables/useCoach.ts
// Place this at: app/composables/useCoach.ts
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'

dayjs.extend(utc)
export const useAdmin = () => {
export const useCoach = () => {
const { classId } = useSelectedClass()
const callFormApi = async <T>(method: 'GET' | 'POST' | 'PUT' | 'DELETE', params: Record<string, unknown> = {}, body?: Record<string, unknown>): Promise<T> => {
if (!classId.value) {
throw new Error('Select a class before managing forms')
}

const scopedParams = {
...params,
...(method === 'DELETE' ? body : {}),
classId: classId.value,
}
const scopedBody = { ...body, classId: classId.value }
const queryString = method === 'GET' || method === 'DELETE'
? `?${new URLSearchParams(Object.entries(params).reduce((acc, [key, value]) => {
? `?${new URLSearchParams(Object.entries(scopedParams).reduce((acc, [key, value]) => {
if (value !== undefined && value !== null) {
acc[key] = String(value)
}
Expand All @@ -18,7 +29,7 @@ export const useAdmin = () => {

return await $fetch<T>(`/api/form${queryString}`, {
method,
body: method === 'GET' ? undefined : body,
body: method === 'GET' || method === 'DELETE' ? undefined : scopedBody,
})
}

Expand Down Expand Up @@ -215,6 +226,11 @@ export const useAdmin = () => {
)

const loadPublishedForms = async () => {
if (!classId.value) {
publishedForms.value = []
return
}

try {
const forms = await callFormApi<any[]>('GET', {
action: 'listForms',
Expand All @@ -227,6 +243,12 @@ export const useAdmin = () => {
}

const syncGroupRangeFromWeeklyDate = async () => {
if (!classId.value) {
historyGroupStartDate.value = ''
historyGroupEndDate.value = ''
return
}

if (!historyWeekStart.value) {
historyGroupStartDate.value = ''
historyGroupEndDate.value = ''
Expand Down Expand Up @@ -262,8 +284,11 @@ export const useAdmin = () => {
}

watch(
historyWeekStart,
[historyWeekStart, classId],
async () => {
publishedForms.value = []
selectedFormDetails.value = null
editingFormId.value = null
await syncGroupRangeFromWeeklyDate()
await loadPublishedForms()
},
Expand Down Expand Up @@ -433,7 +458,7 @@ export const useAdmin = () => {
questions.value = JSON.parse(JSON.stringify(form.questions))
editingFormId.value = form.id
builderSubTab.value = 'creation'
navigateTo('/admin/builder')
navigateTo({ path: '/coach/builder', query: { class: classId.value } })
}

const toggleFormPublish = async (form: any) => {
Expand All @@ -442,9 +467,10 @@ export const useAdmin = () => {
form.status = form.status === 'Active' ? 'Unpublished' : 'Active'
const newStatus = form.status === 'Active' ? true : false

await useFetch(`/api/form/${form.id}`, {
method: 'PUT',
body: {published: newStatus}
await callFormApi('PUT', {}, {
action: 'updateForm',
id: form.id,
published: newStatus,
})
}

Expand All @@ -453,11 +479,45 @@ export const useAdmin = () => {
}

// ── Students / Progress ──
const students = useState<any[]>('adminStudents', () => [
{ id: 1, name: 'Aiden Smith', initials: 'AS', email: 'aiden@school.edu', tickets: 12, streak: 4, lastActive: '2 hours ago' },
{ id: 2, name: 'Nevin Kumar', initials: 'NK', email: 'nevin@school.edu', tickets: 14, streak: 5, lastActive: 'Just now' },
{ id: 3, name: 'Swarna Jay', initials: 'SJ', email: 'swarna@school.edu',tickets: 8, streak: 2, lastActive: 'Yesterday' },
])
const students = useState<any[]>('coachStudents', () => [])

const loadStudents = async () => {
if (!classId.value) {
students.value = []
return
}

const classStudents = await $fetch<Array<{ id: number; name: string; exp: number }>>(
Comment thread
AidanLoran marked this conversation as resolved.
`/api/admin/classes/${classId.value}/students`
Comment thread
AidanLoran marked this conversation as resolved.
Outdated
)

students.value = classStudents.map((student) => ({
...student,
initials: student.name
.split(/\s+/)
.filter(Boolean)
.slice(0, 2)
.map((part) => part[0]?.toUpperCase())
.join(''),
tickets: student.exp,
streak: 0,
lastActive: '',
}))
}

watch(
classId,
async () => {
students.value = []

try {
await loadStudents()
} catch (error) {
console.error('Failed to load class students', error)
}
},
{ immediate: true }
)

const searchStudent = useState('searchStudent', () => '')
const sortStudent = useState('sortStudent', () => 'tickets')
Expand Down
21 changes: 15 additions & 6 deletions app/composables/useCurrentFormGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,26 @@ export const useCurrentFormGroup = () => {
try {
const formGroupAPIResponse = await $fetch<FormGroup | FormGroup[]>('/api/formGroup?active=true')

const activeFg = Array.isArray(formGroupAPIResponse) ? formGroupAPIResponse[0] : formGroupAPIResponse
const activeFormGroups = Array.isArray(formGroupAPIResponse)
Comment thread
AidanLoran marked this conversation as resolved.
Outdated
? formGroupAPIResponse
: formGroupAPIResponse
? [formGroupAPIResponse]
: []
const activeFg = activeFormGroups[0]

if (activeFg) {
FormGroup.value.activeFormGroup = activeFg

try {
const formsAPIResponse = await $fetch<Form[]>('/api/form', {
query: { action: 'getOnlyActiveFormsinGroup', formGroup: activeFg.id }
})
const formResponses = await Promise.all(
activeFormGroups.map((formGroup) =>
$fetch<Form[]>('/api/form', {
query: { action: 'getOnlyActiveFormsinGroup', formGroup: formGroup.id }
})
)
)

FormGroup.value.forms = Array.isArray(formsAPIResponse) ? formsAPIResponse : []
FormGroup.value.forms = formResponses.flat()

// Load form components for each form in parallel
FormGroup.value.formComponents = {}
Expand Down Expand Up @@ -72,4 +81,4 @@ export const useCurrentFormGroup = () => {
loadFormComponents,
totalFormsInGroup
}
}
}
38 changes: 33 additions & 5 deletions app/composables/useRaffleSpin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Student, FormGroup, Form, FormSubmission } from '~~/prisma/generated/client'
export const useRaffleSpin = () => {
const { classId } = useSelectedClass()

const raffleWeekStart = ref<Date | string>(new Date())

Expand All @@ -10,9 +11,17 @@ export const useRaffleSpin = () => {
const spinCount = ref(0)

const loadRaffleFormGroup = async () => {
if (!classId.value) {
raffleFormGroup.value = null
return
}

const val = raffleWeekStart.value
const dateStr = val instanceof Date ? val.toISOString().split('T')[0] : String(val).split('T')[0]
const data = await $fetch<FormGroup>(`/api/formGroup?date=${dateStr}`, { method: 'GET' })
const data = await $fetch<FormGroup>('/api/formGroup', {
method: 'GET',
query: { date: dateStr, classId: classId.value },
})
raffleFormGroup.value = data || null
}

Expand All @@ -21,7 +30,14 @@ export const useRaffleSpin = () => {
raffleForms.value = []
return
}
const data = await $fetch<Form[]>(`/api/form?action=listForms&formGroup=${raffleFormGroup.value.id}`, { method: 'GET' })
const data = await $fetch<Form[]>('/api/form', {
method: 'GET',
query: {
action: 'listForms',
formGroup: raffleFormGroup.value.id,
classId: classId.value,
},
})
raffleForms.value = data || []
}

Expand All @@ -30,7 +46,10 @@ export const useRaffleSpin = () => {
raffleSubmissions.value = []
return
}
const data = await $fetch<FormSubmission[]>(`/api/formSubmission?formGroup=${raffleFormGroup.value.id}`, { method: 'GET' })
const data = await $fetch<FormSubmission[]>('/api/formSubmission', {
method: 'GET',
query: { formGroup: raffleFormGroup.value.id, classId: classId.value },
})
raffleSubmissions.value = data || []
}

Expand Down Expand Up @@ -63,7 +82,8 @@ export const useRaffleSpin = () => {
method: 'PUT',
body: {
id: raffleFormGroup.value.id,
raffleWinner: studentId
raffleWinner: studentId,
classId: classId.value,
}
})
await loadRaffleData()
Expand All @@ -73,6 +93,14 @@ export const useRaffleSpin = () => {
}
}

watch(classId, async () => {
raffleFormGroup.value = null
raffleForms.value = []
raffleSubmissions.value = []
raffleWinner.value = null
await loadRaffleData()
})

return {
raffleWinner,
raffleFormGroup,
Expand All @@ -83,4 +111,4 @@ export const useRaffleSpin = () => {
loadRaffleData,
spinCount
}
}
}
18 changes: 18 additions & 0 deletions app/composables/useSelectedClass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const useSelectedClass = () => {
const route = useRoute()
const storedClassId = useCookie<string | null>('selected-class-token', {
sameSite: 'lax',
})

const classId = computed(() => {
const queryClassId = route.query.class
const normalizedQueryClassId = Array.isArray(queryClassId) ? queryClassId[0] : queryClassId

return normalizedQueryClassId || storedClassId.value || null
})

return {
classId,
storedClassId,
}
}
Loading