add grading, attendnace management

This commit is contained in:
root
2026-04-29 17:39:37 -04:00
parent ea495f8382
commit dc7acf2536
27 changed files with 4150 additions and 960 deletions
+39
View File
@@ -0,0 +1,39 @@
import { apiFetch } from './http'
export type BadgeScanLogRow = {
id: number
user_id: number | null
card_id: string
scan_time: string
school_year: string | null
semester: string | null
user_firstname: string | null
user_lastname: string | null
student_firstname: string | null
student_lastname: string | null
}
export type BadgeScanLogsResponse = {
logs: BadgeScanLogRow[]
}
function unwrap(body: unknown): unknown {
if (body === null || body === undefined || typeof body !== 'object') return body
const o = body as Record<string, unknown>
if ('data' in o && o.data !== null && typeof o.data === 'object') return o.data
return body
}
export async function fetchBadgeScanLogs(): Promise<BadgeScanLogsResponse> {
const raw = await apiFetch<unknown>('/api/v1/badge_scan/logs')
const data = unwrap(raw) as BadgeScanLogsResponse
return { logs: Array.isArray(data?.logs) ? data.logs : [] }
}
export function displayNameOf(row: BadgeScanLogRow): string {
const userFull = [row.user_firstname, row.user_lastname].filter(Boolean).join(' ').trim()
if (userFull) return userFull
const stuFull = [row.student_firstname, row.student_lastname].filter(Boolean).join(' ').trim()
if (stuFull) return stuFull
return row.card_id
}
+82 -14
View File
@@ -1,10 +1,10 @@
/**
* Administrator grading API (parity with CI `Views/grading/*.php`).
* Base: `/api/v1/administrator/grading/...` with JWT.
* Base: `/api/v1/grading/...` with JWT.
*/
import { apiFetch } from './http'
const BASE = '/api/v1/administrator/grading'
const BASE = '/api/v1/grading'
export type GradingScoreRow = {
id?: number
@@ -12,40 +12,108 @@ export type GradingScoreRow = {
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<string, number | string | null>
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 }
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<string, unknown>
}
function q(sp: URLSearchParams): string {
const s = sp.toString()
return s ? `?${s}` : ''
}
export async function fetchGradingMain(searchParams: URLSearchParams): Promise<unknown> {
return apiFetch(`${BASE}/main${q(searchParams)}`)
return apiFetch(`${BASE}/overview${q(searchParams)}`)
}
export async function fetchGradingScoreForm(
scoreType: string,
studentId: string,
classSectionId: string,
studentId: string,
searchParams: URLSearchParams,
): Promise<GradingScoreFormPayload> {
const qs = new URLSearchParams({
student_id: studentId,
class_section_id: classSectionId,
})
return apiFetch(`${BASE}/scores/${encodeURIComponent(scoreType)}?${qs}`)
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<GradingSectionScorePayload> {
const next = new URLSearchParams(searchParams)
next.set('class_section_id', classSectionId)
return apiFetch(`${scoreCollectionPath(scoreType)}${q(next)}`)
}
export async function postGradingScoreUpdate(
body: Record<string, unknown>,
): Promise<unknown> {
return apiFetch(`${BASE}/scores`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
}
export async function postGradingSectionScoreUpdate(
scoreType: string,
body: Record<string, unknown>,
): Promise<unknown> {
return apiFetch(`${BASE}/scores/${encodeURIComponent(scoreType)}`, {
method: 'POST',
const method = scoreType === 'comments' ? 'PUT' : 'POST'
return apiFetch(scoreCollectionPath(scoreType), {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
@@ -89,11 +157,11 @@ export async function fetchHomeworkTracking(
}
export async function fetchParticipation(searchParams: URLSearchParams): Promise<unknown> {
return apiFetch(`${BASE}/participation${q(searchParams)}`)
return apiFetch(`/api/v1/scores/participation${q(searchParams)}`)
}
export async function postParticipation(body: Record<string, unknown>): Promise<unknown> {
return apiFetch(`${BASE}/participation`, {
return apiFetch('/api/v1/scores/participation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
+51 -12
View File
@@ -85,32 +85,51 @@ export async function fetchManualPayPage(params: {
const qs = new URLSearchParams()
qs.set('search_term', params.search_term)
if (params.page != null) qs.set('page', String(params.page))
return apiFetch(`/api/v1/administrator/payments/manual-pay?${qs}`)
return apiFetch(`/api/v1/finance/manual-pay/search?${qs}`)
}
type ManualPaySuggestRow = { id?: number | string; label?: string; email?: string; phone?: string }
export async function fetchManualPaySuggest(q: string): Promise<ManualPaySuggestRow[]> {
const qs = new URLSearchParams({ q })
const body = await apiFetch<{ suggestions?: unknown[]; data?: unknown[] }>(
`/api/v1/administrator/payments/manual-pay/suggest?${qs}`,
const body = await apiFetch<{ items?: unknown[]; suggestions?: unknown[]; data?: unknown[] }>(
`/api/v1/finance/manual-pay/suggest?${qs}`,
)
const raw = body.suggestions ?? body.data ?? []
const raw = body.items ?? body.suggestions ?? body.data ?? []
return Array.isArray(raw) ? (raw as ManualPaySuggestRow[]) : []
}
export async function submitManualPayUpdate(formData: FormData): Promise<{ ok?: boolean; message?: string }> {
return postMultipart('/api/v1/administrator/payments/manual-pay/update', formData)
const invoiceId = String(formData.get('invoice_id') ?? '').trim()
if (!invoiceId) {
throw new ApiHttpError('Invoice is required.', 422, { errors: { invoice_id: ['Invoice is required.'] } })
}
const payload = new FormData()
const amount = formData.get('amount') ?? formData.get('paid_amount')
if (amount != null) payload.set('amount', String(amount))
for (const [key, value] of formData.entries()) {
if (key === 'invoice_id' || key === 'paid_amount') continue
payload.append(key, value)
}
return postMultipart(`/api/v1/finance/manual-pay/invoices/${encodeURIComponent(invoiceId)}/payments`, payload)
}
export async function submitManualPayEdit(formData: FormData): Promise<{ ok?: boolean; message?: string }> {
return postMultipart('/api/v1/administrator/payments/manual-pay/edit', formData)
const paymentId = String(formData.get('payment_id') ?? '').trim()
if (!paymentId) {
throw new ApiHttpError('Payment is required.', 422, { errors: { payment_id: ['Payment is required.'] } })
}
formData.delete('payment_id')
return apiFetch(`/api/v1/finance/manual-pay/payments/${encodeURIComponent(paymentId)}`, {
method: 'PUT',
body: formData,
})
}
/** Authenticated URL for embedding or downloading check/card files (legacy CI: serveCheckFile). */
export function manualPayCheckFileUrl(tokenOrKey: string, mode: 'inline' | 'download'): string {
const enc = encodeURIComponent(tokenOrKey)
return `/api/v1/administrator/payments/check-file/${enc}/${mode}`
return `/api/v1/finance/manual-pay/files/${enc}?mode=${encodeURIComponent(mode)}`
}
export async function fetchAuthenticatedBlobUrl(apiPath: string): Promise<string> {
@@ -213,21 +232,41 @@ export async function fetchExtraChargesPage(params?: {
if (params?.page != null) qs.set('page', String(params.page))
if (params?.parent_id != null) qs.set('parent_id', String(params.parent_id))
const suffix = qs.toString() ? `?${qs}` : ''
return apiFetch(`/api/v1/administrator/charges${suffix}`)
return Promise.all([
apiFetch<{
rows?: ExtraChargeRow[]
school_year?: string
semester?: string
pager?: { current_page?: number; last_page?: number }
}>(`/api/v1/extra-charges${suffix}`),
apiFetch<{
schoolYear?: string
schoolYears?: string[]
semester?: string
parents?: Array<{ id: number; firstname?: string; lastname?: string }>
}>('/api/v1/extra-charges/options'),
]).then(([list, options]) => ({
rows: Array.isArray(list?.rows) ? list.rows : [],
schoolYear: options?.schoolYear ?? list?.school_year,
schoolYears: Array.isArray(options?.schoolYears) ? options.schoolYears : [],
semester: options?.semester ?? list?.semester,
parents: Array.isArray(options?.parents) ? options.parents : [],
pager: list?.pager,
}))
}
export async function fetchChargeInvoicesForParent(parentId: number | string): Promise<
Array<{ id?: number; invoice_number?: string }>
> {
const body = await apiFetch<{ invoices?: unknown[] }>(
`/api/v1/administrator/charges/invoices-for-parent?parent_id=${encodeURIComponent(String(parentId))}`,
const body = await apiFetch<{ results?: unknown[] }>(
`/api/v1/extra-charges/invoices?parent_id=${encodeURIComponent(String(parentId))}`,
)
const raw = body.invoices
const raw = body.results
return Array.isArray(raw) ? (raw as Array<{ id?: number; invoice_number?: string }>) : []
}
export async function submitExtraCharge(formData: FormData): Promise<{ ok?: boolean; message?: string }> {
return postMultipart('/api/v1/administrator/charges', formData)
return postMultipart('/api/v1/extra-charges', formData)
}
export type FinancialDetailedJson = {
+82 -1
View File
@@ -21,9 +21,90 @@ export type CombinedReportPayload = {
[key: string]: unknown
}
type ScorePredictorApiRow = {
student_id?: number
school_id?: string | null
firstname?: string | null
lastname?: string | null
class_section_id?: number | string | null
fall_score?: string | number | null
spring_score?: string | number | null
final_average?: string | number | null
required_spring_score?: string | number | null
winning_risk?: string | null
required_spring_to_pass?: string | number | null
failure_risk?: string | null
status?: string | null
trophy_awarded?: boolean
trophy_reason?: string | null
}
type ScorePredictorApiPayload = {
ok?: boolean
students?: ScorePredictorApiRow[]
school_year?: string | null
class_sections?: Array<Record<string, unknown>>
selected_class_section_id?: number | string | null
semester?: string | null
message?: string | null
}
function mapScorePredictorPayload(raw: ScorePredictorApiPayload): CombinedReportPayload {
const rows = Array.isArray(raw.students)
? raw.students.map((student) => ({
school_id: student.school_id ?? '—',
firstname: student.firstname ?? '—',
lastname: student.lastname ?? '—',
class_section_id: student.class_section_id ?? '—',
fall_score: student.fall_score ?? 'N/A',
spring_score: student.spring_score ?? 'N/A',
final_average: student.final_average ?? 'N/A',
required_spring_score: student.required_spring_score ?? 'N/A',
winning_risk: student.winning_risk ?? '—',
required_spring_to_pass: student.required_spring_to_pass ?? 'N/A',
failure_risk: student.failure_risk ?? '—',
status: student.status ?? '—',
trophy_awarded: student.trophy_awarded ? 'Yes' : 'No',
trophy_reason: student.trophy_reason ?? '—',
}))
: []
const subtitleParts = [
raw.school_year ? `School Year: ${raw.school_year}` : null,
raw.semester ? `Semester: ${raw.semester}` : null,
raw.selected_class_section_id ? `Class Section: ${raw.selected_class_section_id}` : null,
].filter(Boolean)
return {
title: 'Score Analysis',
subtitle: subtitleParts.join(' • '),
message: raw.message ?? undefined,
columns: [
'school_id',
'firstname',
'lastname',
'class_section_id',
'fall_score',
'spring_score',
'final_average',
'required_spring_score',
'winning_risk',
'required_spring_to_pass',
'failure_risk',
'status',
'trophy_awarded',
'trophy_reason',
],
rows,
}
}
export function unwrapCombinedReport(raw: unknown): CombinedReportPayload | null {
if (raw == null || typeof raw !== 'object') return null
const o = raw as ApiEnvelope<CombinedReportPayload> & CombinedReportPayload
if (Array.isArray((raw as ScorePredictorApiPayload).students)) {
return mapScorePredictorPayload(raw as ScorePredictorApiPayload)
}
if (o.data != null && typeof o.data === 'object') {
return o.data as CombinedReportPayload
}
@@ -46,5 +127,5 @@ export async function fetchCombinedReport(params?: {
q.set('class_section_id', String(params.class_section_id))
}
const qs = q.size > 0 ? `?${q.toString()}` : ''
return apiFetch<unknown>(`/api/v1/reports/combined${qs}`)
return apiFetch<unknown>(`/api/v1/scores/predictor${qs}`)
}
+152 -11
View File
@@ -22,6 +22,7 @@ import type {
FamilyAdminIndexResponse,
GradingOverviewResponse,
IpBansResponse,
LandingPageResponse,
LoginFailure,
LoginResponse,
LoginSuccess,
@@ -120,10 +121,69 @@ export async function fetchDashboardRoute(): Promise<ApiEnvelope<DashboardPayloa
return apiFetch<ApiEnvelope<DashboardPayload>>('/api/v1/dashboard/route')
}
export async function fetchLandingAdminDashboard(kind: 'admin' | 'administrator'): Promise<LandingPageResponse> {
return apiFetch<LandingPageResponse>(`/api/v1/landing/${kind}`)
}
export async function fetchAdministratorDashboardMetrics(): Promise<AdministratorDashboardMetricsResponse> {
return apiFetch<AdministratorDashboardMetricsResponse>('/api/v1/administrator/dashboard/metrics')
}
export async function toggleGradingLock(payload: {
class_section_id: number
semester: string
school_year: string
}): Promise<{ ok?: boolean; locked?: boolean }> {
return apiFetch<{ ok?: boolean; locked?: boolean }>('/api/v1/grading/locks/toggle', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function lockAllGradingScores(payload: {
semester: string
school_year: string
}): Promise<{ ok?: boolean; locked_count?: number }> {
return apiFetch<{ ok?: boolean; locked_count?: number }>('/api/v1/grading/locks/all', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function refreshGradingScores(payload: {
class_section_id: number
semester: string
school_year: string
}): Promise<{ ok?: boolean }> {
return apiFetch<{ ok?: boolean }>('/api/v1/grading/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function fetchConfigurationByKey(configKey: string): Promise<{ ok?: boolean; config?: { id?: number; config_key?: string; config_value?: string | null } }> {
return apiFetch<{ ok?: boolean; config?: { id?: number; config_key?: string; config_value?: string | null } }>(
`/api/v1/configuration/key/${encodeURIComponent(configKey)}`,
)
}
export async function updateConfigurationValue(configId: number, payload: {
config_key: string
config_value: string
}): Promise<{ ok?: boolean; config?: { id?: number; config_key?: string; config_value?: string | null } }> {
return apiFetch<{ ok?: boolean; config?: { id?: number; config_key?: string; config_value?: string | null } }>(
`/api/v1/configuration/${configId}`,
{
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
},
)
}
export async function searchAdministratorDashboard(query: string): Promise<AdministratorDashboardSearchResponse> {
const q = query.trim()
const qs = q ? `?query=${encodeURIComponent(q)}` : ''
@@ -146,6 +206,42 @@ export async function submitAdministratorAbsence(payload: {
})
}
/** Laravel often wraps JSON in `{ data: { ... } }`; some clients emit camelCase keys. */
function unwrapApiDataLayer(body: unknown): unknown {
if (body === null || body === undefined || typeof body !== 'object') return body
const o = body as Record<string, unknown>
if ('data' in o && o.data !== null && typeof o.data === 'object') return o.data
return body
}
function normalizeAdministratorDailyAttendanceResponse(raw: unknown): AdministratorDailyAttendanceResponse {
const body = unwrapApiDataLayer(raw) as Record<string, unknown>
const d = { ...(body as unknown as AdministratorDailyAttendanceResponse) }
if (d.grades === undefined && body.Grades != null) d.grades = body.Grades as AdministratorDailyAttendanceResponse['grades']
if (d.students_by_section === undefined && body.studentsBySection != null) {
d.students_by_section = body.studentsBySection as AdministratorDailyAttendanceResponse['students_by_section']
}
if (d.attendance_data === undefined && body.attendanceData != null) {
d.attendance_data = body.attendanceData as AdministratorDailyAttendanceResponse['attendance_data']
}
if (d.attendance_record === undefined && body.attendanceRecord != null) {
d.attendance_record = body.attendanceRecord as AdministratorDailyAttendanceResponse['attendance_record']
}
if (d.dates_by_section === undefined && body.datesBySection != null) {
d.dates_by_section = body.datesBySection as AdministratorDailyAttendanceResponse['dates_by_section']
}
if (d.date_list === undefined && body.dateList != null) {
d.date_list = body.dateList as AdministratorDailyAttendanceResponse['date_list']
}
if (d.school_year === undefined && body.schoolYear != null) {
d.school_year = String(body.schoolYear)
}
if (d.total_passed_days === undefined && body.totalPassedDays != null) {
d.total_passed_days = Number(body.totalPassedDays)
}
return d
}
export async function fetchAdministratorDailyAttendance(
params?: { semester?: string | null; schoolYear?: string | null },
): Promise<AdministratorDailyAttendanceResponse> {
@@ -153,7 +249,8 @@ export async function fetchAdministratorDailyAttendance(
if (params?.semester) query.set('semester', params.semester)
if (params?.schoolYear) query.set('school_year', params.schoolYear)
const qs = query.size > 0 ? `?${query.toString()}` : ''
return apiFetch<AdministratorDailyAttendanceResponse>(`/api/v1/attendance/admin/daily${qs}`)
const raw = await apiFetch<unknown>(`/api/v1/attendance/admin/daily${qs}`)
return normalizeAdministratorDailyAttendanceResponse(raw)
}
export async function addAdministratorAttendanceEntry(payload: {
@@ -295,7 +392,8 @@ export async function fetchAttendanceViolationsPending(params?: {
if (params?.schoolYear) query.set('school_year', params.schoolYear)
if (params?.semester) query.set('semester', params.semester)
const qs = query.size > 0 ? `?${query.toString()}` : ''
return apiFetch<AttendanceViolationsResponse>(`/api/v1/attendance/violations/pending${qs}`)
const raw = await apiFetch<unknown>(`/api/v1/attendance-tracking/pending-violations${qs}`)
return unwrapApiDataLayer(raw) as AttendanceViolationsResponse
}
export async function fetchAttendanceViolationsNotified(params?: {
@@ -306,7 +404,8 @@ export async function fetchAttendanceViolationsNotified(params?: {
if (params?.schoolYear) query.set('school_year', params.schoolYear)
if (params?.semester) query.set('semester', params.semester)
const qs = query.size > 0 ? `?${query.toString()}` : ''
return apiFetch<AttendanceViolationsResponse>(`/api/v1/attendance/violations/notified${qs}`)
const raw = await apiFetch<unknown>(`/api/v1/attendance-tracking/notified-violations${qs}`)
return unwrapApiDataLayer(raw) as AttendanceViolationsResponse
}
export async function fetchParentAttendanceReportsAdmin(params?: {
@@ -411,7 +510,7 @@ export async function deleteAttendanceCommentTemplate(id: number): Promise<{ sta
export async function saveAttendanceViolationNote(payload: Record<string, string>): Promise<{ message?: string }> {
const body = new URLSearchParams()
for (const [k, v] of Object.entries(payload)) body.set(k, v)
return apiFetch('/api/v1/attendance/violations/save-note', {
return apiFetch('/api/v1/attendance-tracking/save-notification-note', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(),
@@ -421,7 +520,7 @@ export async function saveAttendanceViolationNote(payload: Record<string, string
export async function sendAttendanceViolationEmail(payload: Record<string, string>): Promise<{ message?: string }> {
const body = new URLSearchParams()
for (const [k, v] of Object.entries(payload)) body.set(k, v)
return apiFetch('/api/v1/attendance/violations/send-email-manual', {
return apiFetch('/api/v1/attendance-tracking/send-manual-email', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(),
@@ -439,7 +538,7 @@ export async function fetchAttendanceComposeEmailContext(params: {
q.set('code', params.code)
if (params.variant) q.set('variant', params.variant)
if (params.incidentDate) q.set('incident_date', params.incidentDate)
return apiFetch(`/api/v1/attendance/violations/compose?${q.toString()}`)
return apiFetch(`/api/v1/attendance-tracking/compose?${q.toString()}`)
}
export async function fetchAttendanceStudentViolationsView(
@@ -451,7 +550,7 @@ export async function fetchAttendanceStudentViolationsView(
if (query?.incident_date) q.set('incident_date', query.incident_date)
if (query?.include_notified) q.set('include_notified', query.include_notified)
const qs = q.size > 0 ? `?${q.toString()}` : ''
return apiFetch(`/api/v1/attendance/violations/student/${studentId}${qs}`)
return apiFetch(`/api/v1/attendance-tracking/student-case/${studentId}${qs}`)
}
export async function fetchAttendanceTrackingStudents(): Promise<{
@@ -560,6 +659,46 @@ export async function fetchStudentAssignments(
return apiFetch<StudentAssignmentsResponse>(`/api/v1/students/assignments${query}`)
}
export async function fetchClassAssignmentOverview(
schoolYear?: string | null,
semester?: string | null,
): Promise<{
message?: string
data?: {
classSections?: Array<{
class_section_id?: number | null
class_section_name?: string | null
main_teachers?: string[]
teacher_assistants?: string[]
students?: Array<{
id?: number
firstname?: string | null
lastname?: string | null
age?: number | string | null
gender?: string | null
photo_consent?: boolean | number | null
tuition_paid?: boolean | number | null
school_id?: string | null
}>
semester?: string | null
school_year?: string | null
description?: string | null
}>
schoolYears?: string[]
schoolYear?: string | null
selectedYear?: string | null
selectedSemester?: string | null
semester?: string | null
current_school_year?: string | null
}
}> {
const query = new URLSearchParams()
if (schoolYear) query.set('school_year', schoolYear)
if (semester) query.set('semester', semester)
const qs = query.size > 0 ? `?${query.toString()}` : ''
return apiFetch(`/api/v1/assignments${qs}`)
}
export async function assignStudentClass(payload: {
student_id: number
class_section_ids?: number[]
@@ -1942,7 +2081,7 @@ export async function fetchRegisteredNewStudents(params?: {
const suffix = qs.toString() ? `?${qs}` : ''
const body = await apiFetch<
RegisteredNewStudentsResponse & { data?: RegisteredNewStudentsResponse }
>(`/api/v1/administrator/enroll-withdrawal/registered-new-students${suffix}`)
>(`/api/v1/administrator/enroll-withdrawal/new-students${suffix}`)
const d = unwrapData(body)
return {
new_students: d.new_students ?? [],
@@ -1972,7 +2111,7 @@ export async function postEnrollmentWithdrawalAssignClass(payload: {
parent_id: number
class_section_id: number | string
}): Promise<{ ok?: boolean; message?: string }> {
return apiFetch(`/api/v1/administrator/enroll-withdrawal/assign-class`, {
return apiFetch(`/api/v1/administrator/enroll-withdrawal`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
@@ -1984,10 +2123,12 @@ export async function postEnrollmentWithdrawalStatusBatch(payload: {
school_year?: string
semester?: string
}): Promise<{ ok?: boolean; message?: string }> {
return apiFetch(`/api/v1/administrator/enroll-withdrawal/status-batch`, {
return apiFetch(`/api/v1/administrator/enroll-withdrawal/update-statuses`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
body: JSON.stringify({
enrollment_status: payload.updates.map((row) => row.enrollment_status),
}),
})
}
+4 -1
View File
@@ -13,5 +13,8 @@ export async function fetchSlipPreviewList(params?: {
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/slips/preview${suffix}`)
const res = await apiFetch<{ ok: boolean; logs?: SlipPreviewRow[] }>(
`/api/v1/reports/slips/logs${suffix}`,
)
return { rows: res.logs ?? [] }
}
+13 -1
View File
@@ -34,6 +34,18 @@ export type DashboardPayload = {
}
}
export type LandingPageRecord = {
role?: string | null
dashboard?: string | null
summary?: Record<string, unknown> | null
}
export type LandingPagePayload = {
landing?: LandingPageRecord
}
export type LandingPageResponse = ApiEnvelope<LandingPagePayload>
export type NavItem = {
id: number
menu_parent_id: number | null
@@ -182,7 +194,7 @@ export type AdministratorDailyAttendanceResponse = {
grades?: Record<string, AdministratorDailyAttendanceSection[]>
dates_by_section?: Record<string, string[]>
date_list?: string[]
no_school_days?: Record<string, boolean>
no_school_days?: Record<string, string>
total_passed_days?: number
semester?: string | null
school_year?: string | null
+6 -6
View File
@@ -1,10 +1,10 @@
/**
* WhatsApp admin tools (legacy CI `whatsapp/*`).
* Expected Laravel routes under `/api/v1/administrator/whatsapp`.
* Expected Laravel routes under `/api/v1/whatsapp`.
*/
import { apiFetch } from './http'
const BASE = '/api/v1/administrator/whatsapp'
const BASE = '/api/v1/whatsapp'
export type WhatsappSectionRow = {
class_section_id?: number
@@ -80,7 +80,7 @@ export async function fetchWhatsappManageLinks(params?: {
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(`${BASE}/manage-links${suffix}`)
return apiFetch(`${BASE}/links${suffix}`)
}
export async function saveWhatsappLink(payload: {
@@ -89,7 +89,7 @@ export async function saveWhatsappLink(payload: {
invite_link: string
active?: boolean
}): Promise<{ ok?: boolean; message?: string }> {
return apiFetch(`${BASE}/save-link`, {
return apiFetch(`${BASE}/links`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
@@ -101,7 +101,7 @@ export async function sendWhatsappInvites(payload: {
class_section_id?: number | null
parent_ids?: number[]
}): Promise<{ ok?: boolean; message?: string }> {
return apiFetch(`${BASE}/send-invites`, {
return apiFetch(`${BASE}/invites/send`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
@@ -132,7 +132,7 @@ export async function updateWhatsappMembership(payload: {
primary_member?: string
second_member?: string
}): Promise<{ status?: string; message?: string }> {
return apiFetch(`${BASE}/update-membership`, {
return apiFetch(`${BASE}/membership`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),