360 lines
13 KiB
TypeScript
360 lines
13 KiB
TypeScript
/**
|
|
* 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<StudentOption[]> {
|
|
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<Blob> {
|
|
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<Blob> {
|
|
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<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/reports/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<{
|
|
active_role?: string
|
|
rolesTabs?: Record<string, string>
|
|
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<string, string>
|
|
schoolYears?: string[]
|
|
selectedYear?: string
|
|
selectedStudentIds?: number[]
|
|
selectedUserIds?: number[]
|
|
users?: BadgeUserRow[]
|
|
}
|
|
} & {
|
|
active_role?: string
|
|
rolesTabs?: Record<string, string>
|
|
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<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))
|
|
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<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}` : ''
|
|
const body = await apiFetch<ReportCardMeta & { data?: ReportCardMeta }>(`/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<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 ?? '')
|
|
const body = await apiFetch<{
|
|
data?: { students?: Array<Record<string, unknown>>; summary?: Record<string, unknown>; used_fallback?: boolean }
|
|
students?: Array<Record<string, unknown>>
|
|
summary?: Record<string, unknown>
|
|
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<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}`)
|
|
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<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}`)
|
|
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<Blob> {
|
|
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<void> {
|
|
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<void> {
|
|
openBlob(
|
|
await fetchReportCardPdf(
|
|
reportCardClassPath(classSectionId, {
|
|
...opts,
|
|
download: true,
|
|
}),
|
|
),
|
|
)
|
|
}
|