fix teacher, parent and admin pages

This commit is contained in:
root
2026-04-25 00:00:10 -04:00
parent 7fe34dde0d
commit 3e77fc92c7
275 changed files with 46412 additions and 3325 deletions
+250
View File
@@ -0,0 +1,250 @@
/**
* Stickers, badges, report cards (legacy CI printables_reports/*).
*/
import { apiUrl } from '../lib/apiOrigin'
import { ApiHttpError, apiFetch, getStoredToken } from './http'
export type ClassOption = { class_section_id?: number; id?: number; class_section_name?: string }
export type StudentOption = {
id: number | string
firstname?: string
lastname?: string
registration_grade?: string
gender?: string
}
export async function fetchStickerFormOptions(params?: {
school_year?: string
semester?: string
}): Promise<{
classes?: ClassOption[]
students?: StudentOption[]
}> {
const qs = new URLSearchParams()
if (params?.school_year) qs.set('school_year', params.school_year)
if (params?.semester) qs.set('semester', params.semester)
const suffix = qs.toString() ? `?${qs}` : ''
return apiFetch(`/api/v1/administrator/printables/stickers/form-options${suffix}`)
}
export async function fetchStudentsByClass(classId: number | string): Promise<StudentOption[]> {
const body = await apiFetch<{ students?: StudentOption[] } | StudentOption[]>(
`/api/v1/administrator/students/by-class/${encodeURIComponent(String(classId))}`,
)
if (Array.isArray(body)) return body
return body.students ?? []
}
export async function postStickerGenerate(formData: FormData): Promise<Blob> {
const headers = new Headers({ Accept: 'application/pdf,*/*' })
const token = getStoredToken()
if (token) headers.set('Authorization', `Bearer ${token}`)
const res = await fetch(apiUrl('/api/v1/administrator/printables/stickers/generate'), {
method: 'POST',
headers,
body: formData,
})
if (!res.ok) {
const errBody: unknown = await res.json().catch(() => null)
const msg =
typeof errBody === 'object' && errBody !== null && 'message' in errBody
? String((errBody as { message?: unknown }).message ?? '')
: ''
throw new ApiHttpError(msg || `HTTP ${res.status}`, res.status, errBody)
}
return res.blob()
}
export async function postBadgeGenerate(formData: FormData): Promise<Blob> {
const headers = new Headers({ Accept: 'application/pdf,*/*' })
const token = getStoredToken()
if (token) headers.set('Authorization', `Bearer ${token}`)
const res = await fetch(apiUrl('/api/v1/administrator/printables/badges/generate'), {
method: 'POST',
headers,
body: formData,
})
if (!res.ok) {
const errBody: unknown = await res.json().catch(() => null)
const msg =
typeof errBody === 'object' && errBody !== null && 'message' in errBody
? String((errBody as { message?: unknown }).message ?? '')
: ''
throw new ApiHttpError(msg || `HTTP ${res.status}`, res.status, errBody)
}
return res.blob()
}
export type StickerPreviewCounts = {
students?: Array<{ student_id: number | string; primary_count?: number }>
totals?: { stickers?: number; students?: number }
}
export async function fetchStickerPreviewCounts(classId?: number | string | null): Promise<StickerPreviewCounts> {
const qs = new URLSearchParams()
if (classId) qs.set('class_id', String(classId))
const suffix = qs.toString() ? `?${qs}` : ''
const body = await apiFetch<
StickerPreviewCounts & { status?: string; data?: StickerPreviewCounts }
>(`/api/v1/administrator/printables/stickers/preview${suffix}`)
if (body.data) return body.data
return body
}
export type BadgeUserRow = Record<string, unknown>
export async function fetchBadgeFormOptions(params?: {
school_year?: string
semester?: string
}): Promise<{
selectedYear?: string
users?: BadgeUserRow[]
}> {
const qs = new URLSearchParams()
if (params?.school_year) qs.set('school_year', params.school_year)
if (params?.semester) qs.set('semester', params.semester)
const suffix = qs.toString() ? `?${qs}` : ''
return apiFetch(`/api/v1/administrator/printables/badges/form-options${suffix}`)
}
export async function fetchBadgePrintStatus(params: {
user_ids: (number | string)[]
school_year?: string
}): Promise<{
ok?: boolean
data?: Record<string, { count?: number }>
csrf_token?: string
csrf_hash?: string
}> {
const qs = new URLSearchParams()
for (const id of params.user_ids) qs.append('user_ids[]', String(id))
if (params.school_year) qs.set('school_year', params.school_year)
return apiFetch(`/api/v1/administrator/printables/badges/status?${qs}`)
}
export type ReportCardMeta = {
ok?: boolean
schoolYears?: string[]
semesters?: string[]
selectedYear?: string
selectedSemester?: string
students?: Array<{ id: number | string; firstname?: string; lastname?: string; class_section_name?: string }>
classSections?: Array<{ class_section_id?: number; id?: number; class_section_name?: string }>
}
export async function fetchReportCardMeta(params?: {
school_year?: string
semester?: string
}): Promise<ReportCardMeta> {
const qs = new URLSearchParams()
if (params?.school_year) qs.set('school_year', params.school_year)
if (params?.semester) qs.set('semester', params.semester)
const suffix = qs.toString() ? `?${qs}` : ''
return apiFetch(`/api/v1/administrator/printables/report-card/meta${suffix}`)
}
export async function fetchReportCardAck(params: {
student_id: number | string
school_year?: string
semester?: string
}): Promise<{ ok?: boolean; viewed_at?: string; signed_at?: string; signed_name?: string }> {
const qs = new URLSearchParams()
qs.set('student_id', String(params.student_id))
if (params.school_year) qs.set('school_year', params.school_year)
if (params.semester) qs.set('semester', params.semester ?? '')
return apiFetch(`/api/v1/administrator/printables/report-card/ack?${qs}`)
}
export async function fetchReportCardCompleteness(params: {
class_section_id: number | string
school_year?: string
semester?: string
}): Promise<{
ok?: boolean
students?: Array<Record<string, unknown>>
summary?: Record<string, unknown>
used_fallback?: boolean
}> {
const qs = new URLSearchParams()
qs.set('class_section_id', String(params.class_section_id))
if (params.school_year) qs.set('school_year', params.school_year ?? '')
if (params.semester) qs.set('semester', params.semester ?? '')
return apiFetch(`/api/v1/administrator/printables/report-card/completeness?${qs}`)
}
/** Build absolute API URL for iframe / window.open (report PDF HTML). */
export function reportCardStudentUrl(
studentId: number | string,
opts: { school_year: string; semester?: string; download?: boolean; report_date?: string },
): string {
const qs = new URLSearchParams()
qs.set('school_year', opts.school_year)
if (opts.semester) qs.set('semester', opts.semester)
if (opts.download) qs.set('download', '1')
if (opts.report_date) qs.set('report_date', opts.report_date)
qs.set('cb', String(Date.now()))
return apiUrl(`/api/v1/reports/report-cards/students/${encodeURIComponent(String(studentId))}?${qs}`)
}
export function reportCardClassUrl(
classSectionId: number | string,
opts: { school_year: string; semester?: string; download?: boolean; report_date?: string },
): string {
const qs = new URLSearchParams()
qs.set('school_year', opts.school_year)
if (opts.semester) qs.set('semester', opts.semester)
if (opts.download) qs.set('download', '1')
if (opts.report_date) qs.set('report_date', opts.report_date)
qs.set('cb', String(Date.now()))
return apiUrl(`/api/v1/reports/report-cards/classes/${encodeURIComponent(String(classSectionId))}?${qs}`)
}
/** HTML document for iframe preview (Bearer auth; unlike raw iframe src). */
export async function fetchReportCardStudentHtml(
studentId: number | string,
opts: { school_year: string; semester?: string; report_date?: string },
): Promise<string> {
const qs = new URLSearchParams()
qs.set('school_year', opts.school_year)
if (opts.semester) qs.set('semester', opts.semester)
if (opts.report_date) qs.set('report_date', opts.report_date)
qs.set('cb', String(Date.now()))
const headers = new Headers({ Accept: 'text/html' })
const token = getStoredToken()
if (token) headers.set('Authorization', `Bearer ${token}`)
const path = `/api/v1/reports/report-cards/students/${encodeURIComponent(String(studentId))}?${qs}`
const res = await fetch(apiUrl(path), { headers })
if (!res.ok) throw new ApiHttpError(`HTTP ${res.status}`, res.status, null)
return res.text()
}
export async function fetchReportCardClassHtml(
classSectionId: number | string,
opts: { school_year: string; semester?: string; report_date?: string },
): Promise<string> {
const qs = new URLSearchParams()
qs.set('school_year', opts.school_year)
if (opts.semester) qs.set('semester', opts.semester)
if (opts.report_date) qs.set('report_date', opts.report_date)
qs.set('cb', String(Date.now()))
const headers = new Headers({ Accept: 'text/html' })
const token = getStoredToken()
if (token) headers.set('Authorization', `Bearer ${token}`)
const path = `/api/v1/reports/report-cards/classes/${encodeURIComponent(String(classSectionId))}?${qs}`
const res = await fetch(apiUrl(path), { headers })
if (!res.ok) throw new ApiHttpError(`HTTP ${res.status}`, res.status, null)
return res.text()
}
/** Opens PDF download in new tab (authenticated GET). */
export async function openReportCardDownload(url: string): Promise<void> {
const headers = new Headers({ Accept: 'application/pdf,*/*' })
const token = getStoredToken()
if (token) headers.set('Authorization', `Bearer ${token}`)
const res = await fetch(url, { headers })
if (!res.ok) throw new ApiHttpError(`HTTP ${res.status}`, res.status, null)
const blob = await res.blob()
const r = URL.createObjectURL(blob)
window.open(r, '_blank', 'noopener')
}