add grading, attendnace management
This commit is contained in:
@@ -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
@@ -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),
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user