/** * Administrator grading API (parity with CI `Views/grading/*.php`). * Base: `/api/v1/grading/...` with JWT. */ import { apiFetch } from './http' const BASE = '/api/v1/grading' export type GradingScoreRow = { id?: number score?: number | string | null comment?: string | null } export type GradingSectionScoreStudent = { student_id: number firstname?: string | null lastname?: string | null school_id?: string | null score?: number | string | null scores?: Record comments?: Record< string, { comment?: string | null comment_review?: string | null commented_by?: string | number | null reviewed_by?: string | number | null } > } export type GradingScoreFormPayload = { scoresLocked?: boolean student?: { id?: number; firstname?: string; lastname?: string; school_id?: string | null } classSectionId?: number class_section_name?: string | null scores?: GradingScoreRow[] } export type GradingSectionScorePayload = { ok?: boolean students?: GradingSectionScoreStudent[] headers?: number[] semester?: string school_year?: string class_section_id?: number | string class_section_name?: string | null scores_locked?: boolean missing_ok_map?: Record } export type AllDecisionRow = { student_id: number school_id?: string | null firstname?: string | null lastname?: string | null gender?: string | null class_section_id?: number | string | null class_section_name?: string | null fall_score?: number | string | null spring_score?: number | string | null year_score?: number | string | null decision?: string | null source?: string | null notes?: string | null saved?: boolean is_trophy?: boolean } export type AllDecisionsPayload = { ok?: boolean rows?: AllDecisionRow[] generated?: boolean school_year?: string } function q(sp: URLSearchParams): string { const s = sp.toString() return s ? `?${s}` : '' } export async function fetchGradingMain(searchParams: URLSearchParams): Promise { return apiFetch(`${BASE}/overview${q(searchParams)}`) } export async function fetchGradingScoreForm( scoreType: string, classSectionId: string, studentId: string, searchParams: URLSearchParams, ): Promise { return apiFetch(`${BASE}/scores/${encodeURIComponent(scoreType)}/${encodeURIComponent(classSectionId)}/${encodeURIComponent(studentId)}${q(searchParams)}`) } function scoreCollectionPath(scoreType: string): string { switch (scoreType) { case 'homework': return '/api/v1/scores/homework' case 'quiz': return '/api/v1/scores/quizzes' case 'project': return '/api/v1/scores/projects' case 'midterm': return '/api/v1/scores/midterm' case 'final': return '/api/v1/scores/final' case 'comments': return '/api/v1/scores/comments' default: throw new Error(`Unsupported score type: ${scoreType}`) } } export async function fetchGradingSectionScoreTable( scoreType: string, classSectionId: string, searchParams: URLSearchParams, ): Promise { const next = new URLSearchParams(searchParams) next.set('class_section_id', classSectionId) return apiFetch(`${scoreCollectionPath(scoreType)}${q(next)}`) } export async function postGradingScoreUpdate( body: Record, ): Promise { return apiFetch(`${BASE}/scores`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) } export async function postGradingSectionScoreUpdate( scoreType: string, body: Record, ): Promise { const method = scoreType === 'comments' ? 'PUT' : 'POST' return apiFetch(scoreCollectionPath(scoreType), { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) } export async function fetchBelowSixty(searchParams: URLSearchParams): Promise { return apiFetch(`${BASE}/below-sixty${q(searchParams)}`) } export async function fetchAllDecisions( searchParams: URLSearchParams, ): Promise { return apiFetch(`${BASE}/decisions${q(searchParams)}`) } export async function postGenerateAllDecisions(body: Record): Promise<{ ok?: boolean saved_count?: number }> { return apiFetch(`${BASE}/decisions/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) } export async function fetchBelowSixtyDecisions( searchParams: URLSearchParams, ): Promise { return apiFetch(`${BASE}/below-sixty/decisions${q(searchParams)}`) } export async function postBelowSixtyDecision(body: Record): Promise { return apiFetch(`${BASE}/below-sixty/decisions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) } export async function fetchBelowSixtyDecisionDetails( searchParams: URLSearchParams, ): Promise { return apiFetch(`${BASE}/below-sixty/decisions/details${q(searchParams)}`) } export async function fetchBelowSixtyDecisionEmailEditor( searchParams: URLSearchParams, ): Promise<{ student_id?: number student_name?: string school_year?: string semester?: string decision?: string subject?: string html?: string year_score?: number | string | null }> { return apiFetch(`${BASE}/below-sixty/decisions/email${q(searchParams)}`) } export async function postBelowSixtyDecisionEmail(body: Record): Promise { return apiFetch(`${BASE}/below-sixty/decisions/email`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) } export async function fetchBelowSixtyEmailEditor(searchParams: URLSearchParams): Promise<{ studentId?: number studentName?: string semester?: string schoolYear?: string subject?: string html?: string }> { return apiFetch(`${BASE}/below-sixty/email${q(searchParams)}`) } export async function postBelowSixtyEmail(body: Record): Promise { return apiFetch(`${BASE}/below-sixty/email`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) } export async function postBelowSixtyStatus(body: Record): Promise { return apiFetch(`${BASE}/below-sixty/status`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) } export async function fetchHomeworkTracking( searchParams: URLSearchParams, ): Promise { return apiFetch(`${BASE}/homework-tracking${q(searchParams)}`) } export async function fetchParticipation(searchParams: URLSearchParams): Promise { return apiFetch(`/api/v1/scores/participation${q(searchParams)}`) } export async function postParticipation(body: Record): Promise { return apiFetch('/api/v1/scores/participation', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) } export async function fetchPlacementIndex(searchParams: URLSearchParams): Promise { return apiFetch(`${BASE}/placement-index${q(searchParams)}`) } export async function postPlacementAll(body: Record): Promise { return apiFetch(`${BASE}/placement-all`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) } export async function fetchPlacementSection(searchParams: URLSearchParams): Promise { return apiFetch(`${BASE}/placement${q(searchParams)}`) } export async function postPlacementSection(body: Record): Promise { return apiFetch(`${BASE}/placement`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) } export async function fetchPlacementBatch(batchId: string | number): Promise { return apiFetch(`${BASE}/placement/batch/${batchId}`) } export async function postPlacementBatch( batchId: string | number, body: Record, ): Promise { return apiFetch(`${BASE}/placement/batch/${batchId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) } export async function fetchScheduleMeetingForm( searchParams: URLSearchParams, ): Promise { return apiFetch(`${BASE}/below-sixty/meeting${q(searchParams)}`) } export async function postScheduleMeeting(body: Record): Promise { return apiFetch(`${BASE}/below-sixty/meeting`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) }