add parent pages

This commit is contained in:
root
2026-04-23 02:24:05 -04:00
parent 9191fd32f0
commit 94700d4f0f
30 changed files with 8179 additions and 634 deletions
+186 -7
View File
@@ -1,21 +1,33 @@
import { apiFetch } from './http'
import { apiUrl } from '../lib/apiOrigin'
import { apiFetch, getStoredToken } from './http'
import type {
ApiEnvelope,
DashboardPayload,
LoginFailure,
LoginResponse,
LoginSuccess,
NavBuilderDataResponse,
NavBuilderDeleteResponse,
NavBuilderSaveResponse,
NavMenuResponse,
ParentAttendanceResponse,
ParentAttendanceReportFormResponse,
ParentEnrollmentsResponse,
ParentEventsResponse,
ParentEventsOverviewResponse,
ParentInvoicesResponse,
ParentProfileResponse,
ParentRegistrationResponse,
ParentRegistrationOverviewResponse,
ParentEmergencyContactsResponse,
ParentAttendanceReportsListResponse,
ParentAuthorizedUsersApiResponse,
ParentMessagesResponse,
SchoolCalendarEventsResponse,
StudentScoresResponse,
ReportCardAcknowledgementResponse,
ReportCardMetaResponse,
ClassProgressGroupsResponse,
ClassProgressShowResponse,
ParentProfileRecord,
ContactSubmitResponse,
} from './types'
@@ -49,6 +61,42 @@ export async function fetchNavMenu(): Promise<NavMenuResponse> {
return apiFetch<NavMenuResponse>('/api/v1/nav-builder/menu')
}
export async function fetchNavBuilderData(): Promise<NavBuilderDataResponse> {
return apiFetch<NavBuilderDataResponse>('/api/v1/nav-builder/data')
}
export async function saveNavBuilderItem(payload: {
id?: number | null
menu_parent_id?: number | null
label: string
url?: string | null
icon_class?: string | null
target?: string | null
sort_order?: number
is_enabled?: boolean
roles?: number[]
}): Promise<NavBuilderSaveResponse> {
return apiFetch<NavBuilderSaveResponse>('/api/v1/nav-builder', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function deleteNavBuilderItem(id: number): Promise<NavBuilderDeleteResponse> {
return apiFetch<NavBuilderDeleteResponse>(`/api/v1/nav-builder/${id}`, {
method: 'DELETE',
})
}
export async function reorderNavBuilderItems(orders: Record<string, number>): Promise<ApiEnvelope<{ ok: true }>> {
return apiFetch<ApiEnvelope<{ ok: true }>>('/api/v1/nav-builder/reorder', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orders }),
})
}
export async function fetchParentAttendance(
schoolYear: string | null,
): Promise<ParentAttendanceResponse> {
@@ -74,12 +122,12 @@ export async function fetchParentInvoices(
return apiFetch<ParentInvoicesResponse>(`/api/v1/parents/invoices${q}`)
}
export async function fetchParentEventsOverview(): Promise<ParentEventsResponse> {
return apiFetch<ParentEventsResponse>('/api/v1/parents/events')
export async function fetchParentEventsOverview(): Promise<ParentEventsOverviewResponse> {
return apiFetch<ParentEventsOverviewResponse>('/api/v1/parents/events')
}
export async function fetchParentRegistrationOverview(): Promise<ParentRegistrationResponse> {
return apiFetch<ParentRegistrationResponse>('/api/v1/parents/registration')
export async function fetchParentRegistrationOverview(): Promise<ParentRegistrationOverviewResponse> {
return apiFetch<ParentRegistrationOverviewResponse>('/api/v1/parents/registration')
}
export async function fetchAttendanceReportForm(): Promise<ParentAttendanceReportFormResponse> {
@@ -120,3 +168,134 @@ export async function fetchParentAttendanceReportsList(
export async function fetchParentAuthorizedUsers(): Promise<ParentAuthorizedUsersApiResponse> {
return apiFetch<ParentAuthorizedUsersApiResponse>('/api/v1/parents/authorized-users')
}
export async function inviteParentAuthorizedUser(email: string): Promise<ContactSubmitResponse> {
return apiFetch<ContactSubmitResponse>('/api/v1/parents/authorized-users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
})
}
export async function fetchParentMessagesInbox(): Promise<ParentMessagesResponse> {
return apiFetch<ParentMessagesResponse>('/api/v1/messages/inbox')
}
export async function fetchSchoolCalendarEvents(): Promise<SchoolCalendarEventsResponse> {
return apiFetch<SchoolCalendarEventsResponse>(
'/api/v1/settings/school-calendar/events?audience=parent',
)
}
export async function fetchStudentScores(studentId: number): Promise<StudentScoresResponse> {
return apiFetch<StudentScoresResponse>(`/api/v1/students/${studentId}/scores`)
}
export async function fetchReportCardMeta(): Promise<ReportCardMetaResponse> {
return apiFetch<ReportCardMetaResponse>('/api/v1/reports/report-cards/meta')
}
export async function fetchReportCardAcknowledgement(
studentId: number,
schoolYear?: string | null,
semester?: string | null,
): Promise<ReportCardAcknowledgementResponse> {
const query = new URLSearchParams({ student_id: String(studentId) })
if (schoolYear) query.set('school_year', schoolYear)
if (semester) query.set('semester', semester)
return apiFetch<ReportCardAcknowledgementResponse>(
`/api/v1/reports/report-cards/acknowledgement?${query.toString()}`,
)
}
export async function fetchClassProgressGroups(): Promise<ClassProgressGroupsResponse> {
return apiFetch<ClassProgressGroupsResponse>('/api/v1/class-progress?group_by_week=1')
}
export async function fetchClassProgressDetail(id: number): Promise<ClassProgressShowResponse> {
return apiFetch<ClassProgressShowResponse>(`/api/v1/class-progress/${id}`)
}
export async function updateParentProfile(
payload: Required<
Pick<
ParentProfileRecord,
'firstname' | 'lastname' | 'cellphone' | 'address_street' | 'city' | 'state' | 'zip'
>
>,
): Promise<ParentProfileResponse> {
return apiFetch<ParentProfileResponse>('/api/v1/parents/profile', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function updateParentStudent(
studentId: number,
payload: Record<string, unknown>,
): Promise<{ ok: true }> {
return apiFetch<{ ok: true }>(`/api/v1/parents/students/${studentId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function storeParentRegistration(payload: {
students?: Record<string, unknown>[]
emergency_contacts?: Record<string, unknown>[]
}): Promise<{ ok: true; created_student_ids?: number[] }> {
return apiFetch<{ ok: true; created_student_ids?: number[] }>('/api/v1/parents/registration', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function storeParentEmergencyContact(payload: {
name: string
cellphone: string
email?: string
relation?: string
}): Promise<ParentEmergencyContactsResponse> {
return apiFetch<ParentEmergencyContactsResponse>('/api/v1/parents/emergency-contacts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function updateParentEmergencyContact(
contactId: number,
payload: { name: string; cellphone: string; email?: string; relation?: string },
): Promise<ParentEmergencyContactsResponse> {
return apiFetch<ParentEmergencyContactsResponse>(`/api/v1/parents/emergency-contacts/${contactId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function submitParentAttendanceReport(payload: Record<string, unknown>): Promise<{
ok: true
inserted?: number
blocked?: number
}> {
return apiFetch('/api/v1/parents/attendance-reports', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function fetchReportCardPdf(studentId: number): Promise<Blob> {
const headers = new Headers({ Accept: 'application/pdf' })
const token = getStoredToken()
if (token) headers.set('Authorization', `Bearer ${token}`)
const res = await fetch(apiUrl(`/api/v1/reports/report-cards/students/${studentId}`), {
headers,
})
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.blob()
}
+213 -2
View File
@@ -46,6 +46,47 @@ export type NavItem = {
export type NavMenuResponse = ApiEnvelope<{ items: NavItem[] }>
export type NavBuilderRoleOption = {
id: number
name: string
}
export type NavBuilderParentOption = {
id: number
label: string
}
export type NavBuilderItem = {
id: number
label: string
url?: string | null
icon_class?: string | null
target?: string | null
menu_parent_id?: number | null
parent_id?: number | null
parent_label?: string | null
sort_order?: number
order?: number
is_enabled?: number | boolean
enabled?: boolean
depth?: number
roles?: {
ids?: number[]
names?: string[]
}
raw?: Record<string, unknown>
}
export type NavBuilderData = {
items: NavBuilderItem[]
roles: NavBuilderRoleOption[]
parentOptions: NavBuilderParentOption[]
}
export type NavBuilderDataResponse = ApiEnvelope<{ data: NavBuilderData }>
export type NavBuilderSaveResponse = ApiEnvelope<{ id: number }>
export type NavBuilderDeleteResponse = ApiEnvelope<null>
export type ParentAttendanceRow = {
firstname: string
lastname: string
@@ -96,7 +137,13 @@ export type ParentEnrollmentStudent = {
id: number
firstname: string
lastname: string
dob?: string | null
gender?: string | null
registration_grade?: string | null
class_section?: string | null
enrollment_status?: string | null
admission_status?: string | null
disable_enroll?: boolean
}
export type ParentEnrollmentsResponse = {
@@ -114,7 +161,15 @@ export type ContactSubmitResponse = {
/** Loose JSON payloads for complex parent dashboards (events, registration, attendance report form). */
export type ParentEventsResponse = Record<string, unknown>
export type ParentRegistrationResponse = Record<string, unknown>
export type ParentAttendanceReportFormResponse = Record<string, unknown>
export type ParentAttendanceReportFormResponse = {
ok: true
students?: ParentEnrollmentStudent[]
today?: string
sundays?: string[]
defaultDate?: string
myReports?: Record<string, unknown>[]
cutoffThreshold?: string | null
}
export type ParentEmergencyContactRow = {
id: number
@@ -135,4 +190,160 @@ export type ParentAttendanceReportsListResponse = {
}
/** `GET /api/v1/parents/authorized-users` — envelope from BaseApiController. */
export type ParentAuthorizedUsersApiResponse = ApiEnvelope<unknown[]>
export type ParentAuthorizedUserRow = {
id?: number
user_id?: number
email?: string | null
created_at?: string | null
updated_at?: string | null
[key: string]: unknown
}
export type ParentAuthorizedUsersApiResponse = ApiEnvelope<ParentAuthorizedUserRow[]>
export type ParentMessageRow = {
id?: number | null
message_number?: string | number | null
sender_name?: string | null
recipient_name?: string | null
subject?: string | null
message?: string | null
sent_datetime?: string | null
read_status?: boolean
priority?: string | null
}
export type ParentMessagesResponse = ApiEnvelope<{
messages: ParentMessageRow[]
meta?: Record<string, unknown> | null
}>
export type SchoolCalendarEventRow = {
id?: number | null
title: string
start: string
description?: string
extendedProps?: {
event_type?: string
backgroundColor?: string
color?: string
[key: string]: unknown
}
}
export type SchoolCalendarEventsResponse = ApiEnvelope<{
events: SchoolCalendarEventRow[]
}>
export type StudentScoreRow = {
id?: number
student_id?: number
school_year?: string | null
semester?: string | null
class_section_id?: number | null
homework_score?: number | string | null
project_score?: number | string | null
participation_score?: number | string | null
quiz_score?: number | string | null
attendance_score?: number | string | null
ptap_score?: number | string | null
midterm_exam_score?: number | string | null
final_exam_score?: number | string | null
semester_score?: number | string | null
[key: string]: unknown
}
export type StudentScoresResponse = {
ok: true
student_id: number
rows: StudentScoreRow[]
}
export type ReportCardStudentMetaRow = {
id: number
firstname: string
lastname: string
class_section_name: string
}
export type ReportCardMetaResponse = ApiEnvelope<{
schoolYears: string[]
selectedYear: string | null
semesters: string[]
selectedSemester: string | null
students: ReportCardStudentMetaRow[]
}>
export type ReportCardAcknowledgementRow = {
student_id: number
school_year: string
semester: string
viewed_at?: string | null
signed_at?: string | null
signed_name?: string | null
}
export type ReportCardAcknowledgementResponse = ApiEnvelope<ReportCardAcknowledgementRow>
export type ClassProgressReportRow = {
id: number
teacher_name?: string | null
class_section_name?: string | null
subject?: string | null
unit_title?: string | null
status?: string | null
status_label?: string | null
covered?: string | null
homework?: string | null
support_needed?: string | null
week_start?: string | null
week_end?: string | null
attachments?: { id?: number; original_name?: string | null; url?: string | null }[]
}
export type ClassProgressGroupRow = {
week_start?: string | null
week_end?: string | null
class_section_id?: number | null
class_section_name?: string | null
reports?: Record<string, ClassProgressReportRow>
}
export type ClassProgressGroupsResponse = ApiEnvelope<{
items: ClassProgressGroupRow[]
pagination?: Record<string, unknown>
}>
export type ClassProgressShowResponse = ApiEnvelope<{
report?: ClassProgressReportRow
weekly_reports?: Record<string, ClassProgressReportRow>
status_options?: Record<string, string>
}>
export type ParentRegistrationOverviewResponse = {
ok: true
parent: ParentProfileRecord | null
existingKids: ParentEnrollmentStudent[]
emergencies: ParentEmergencyContactRow[]
maxChilds?: number
maxEmergency?: number
lastDayOfRegistration?: string | null
registrationAgeDeadline?: string | null
}
export type ParentEventRow = {
id?: number
title?: string | null
event_name?: string | null
event_date?: string | null
start_date?: string | null
description?: string | null
[key: string]: unknown
}
export type ParentEventsOverviewResponse = {
ok: true
activeEvents?: ParentEventRow[]
charges?: Record<string, unknown>[]
yourStudents?: ParentEnrollmentStudent[]
}