/** * Stickers, badges, report cards (legacy CI printables_reports/*). */ import { apiUrl } from '../lib/apiOrigin' import { ApiHttpError, apiFetch, applyStoredSchoolYearHeaders, 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}` : '' const body = await apiFetch<{ ok?: boolean data?: { classes?: ClassOption[]; students?: StudentOption[] } classes?: ClassOption[] students?: StudentOption[] }>(`/api/v1/reports/stickers/form-data${suffix}`) return body.data ?? body } export async function fetchStudentsByClass(classId: number | string): Promise { const qs = new URLSearchParams({ class_section_id: String(classId) }) const body = await apiFetch<{ ok?: boolean data?: { students?: StudentOption[] } students?: StudentOption[] }>(`/api/v1/reports/stickers/students?${qs}`) return body.data?.students ?? body.students ?? [] } export async function postStickerGenerate(formData: FormData): Promise { const headers = new Headers({ Accept: 'application/pdf,*/*' }) const token = getStoredToken() if (token) headers.set('Authorization', `Bearer ${token}`) applyStoredSchoolYearHeaders(headers) const res = await fetch(apiUrl('/api/v1/reports/stickers/print'), { 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) } const raw = await res.blob() return new Blob([await raw.arrayBuffer()], { type: 'application/pdf' }) } export async function postBadgeGenerate(formData: FormData): Promise { const headers = new Headers({ Accept: 'application/pdf,*/*' }) const token = getStoredToken() if (token) headers.set('Authorization', `Bearer ${token}`) applyStoredSchoolYearHeaders(headers) const res = await fetch(apiUrl('/api/v1/badges/pdf'), { 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) } const raw = await res.blob() return new Blob([await raw.arrayBuffer()], { type: 'application/pdf' }) } 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 { 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/reports/stickers/preview${suffix}`) if (body.data) return body.data return body } export type BadgeUserRow = Record export async function fetchBadgeFormOptions(params?: { school_year?: string semester?: string }): Promise<{ active_role?: string rolesTabs?: Record schoolYears?: string[] selectedYear?: string selectedStudentIds?: number[] selectedUserIds?: number[] 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}` : '' const body = await apiFetch<{ ok?: boolean data?: { active_role?: string rolesTabs?: Record schoolYears?: string[] selectedYear?: string selectedStudentIds?: number[] selectedUserIds?: number[] users?: BadgeUserRow[] } } & { active_role?: string rolesTabs?: Record schoolYears?: string[] selectedYear?: string selectedStudentIds?: number[] selectedUserIds?: number[] users?: BadgeUserRow[] }>(`/api/v1/administrator/printables/badges/form-options${suffix}`) return body.data ?? body } export async function fetchBadgePrintStatus(params: { user_ids?: (number | string)[] student_ids?: (number | string)[] school_year?: string }): Promise<{ ok?: boolean data?: Record csrf_token?: string csrf_hash?: string }> { const qs = new URLSearchParams() for (const id of params.user_ids ?? []) qs.append('user_ids[]', String(id)) for (const id of params.student_ids ?? []) qs.append('student_ids[]', String(id)) if (params.school_year) qs.set('school_year', params.school_year) return apiFetch(`/api/v1/badges/print-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 { 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}` : '' const body = await apiFetch(`/api/v1/administrator/printables/report-card/meta${suffix}`) return body.data ?? body } 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 ?? '') const body = await apiFetch<{ data?: { viewed_at?: string; signed_at?: string; signed_name?: string }; viewed_at?: string; signed_at?: string; signed_name?: string }>(`/api/v1/administrator/printables/report-card/ack?${qs}`) return body.data ?? body } export async function fetchReportCardCompleteness(params: { class_section_id: number | string school_year?: string semester?: string }): Promise<{ ok?: boolean students?: Array> summary?: Record 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 ?? '') const body = await apiFetch<{ data?: { students?: Array>; summary?: Record; used_fallback?: boolean } students?: Array> summary?: Record used_fallback?: boolean }>(`/api/v1/administrator/printables/report-card/completeness?${qs}`) return body.data ?? body } function reportCardStudentPath( 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 `/api/v1/reports/report-cards/students/${encodeURIComponent(String(studentId))}?${qs}` } function reportCardClassPath( 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 `/api/v1/reports/report-cards/classes/${encodeURIComponent(String(classSectionId))}?${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 { return apiUrl(reportCardStudentPath(studentId, opts)) } export function reportCardClassUrl( classSectionId: number | string, opts: { school_year: string; semester?: string; download?: boolean; report_date?: string }, ): string { return apiUrl(reportCardClassPath(classSectionId, opts)) } /** 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 { 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}`) applyStoredSchoolYearHeaders(headers) 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 { 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}`) applyStoredSchoolYearHeaders(headers) 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() } async function fetchReportCardPdf(path: string): Promise { const headers = new Headers({ Accept: 'application/pdf,*/*' }) const token = getStoredToken() if (!token) { throw new ApiHttpError('Your session has expired. Please sign in again.', 401, null) } if (token) headers.set('Authorization', `Bearer ${token}`) applyStoredSchoolYearHeaders(headers) const res = await fetch(apiUrl(path), { headers }) 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) } const raw = await res.blob() return new Blob([await raw.arrayBuffer()], { type: 'application/pdf' }) } function openBlob(blob: Blob): void { const objectUrl = URL.createObjectURL(blob) window.open(objectUrl, '_blank', 'noopener') window.setTimeout(() => URL.revokeObjectURL(objectUrl), 60_000) } /** Opens PDF download in new tab (authenticated GET). */ export async function openReportCardStudentDownload( studentId: number | string, opts: { school_year: string; semester?: string; report_date?: string }, ): Promise { openBlob( await fetchReportCardPdf( reportCardStudentPath(studentId, { ...opts, download: true, }), ), ) } export async function openReportCardClassDownload( classSectionId: number | string, opts: { school_year: string; semester?: string; report_date?: string }, ): Promise { openBlob( await fetchReportCardPdf( reportCardClassPath(classSectionId, { ...opts, download: true, }), ), ) }