security fix and fix parent pages
This commit is contained in:
+5
-5
@@ -216,7 +216,7 @@ export async function fetchBelowSixtyEmailEditor(searchParams: URLSearchParams):
|
||||
subject?: string
|
||||
html?: string
|
||||
}> {
|
||||
return apiFetch(`${BASE}/below-sixty/email-editor${q(searchParams)}`)
|
||||
return apiFetch(`${BASE}/below-sixty/email${q(searchParams)}`)
|
||||
}
|
||||
|
||||
export async function postBelowSixtyEmail(body: Record<string, unknown>): Promise<unknown> {
|
||||
@@ -278,14 +278,14 @@ export async function postPlacementSection(body: Record<string, unknown>): Promi
|
||||
}
|
||||
|
||||
export async function fetchPlacementBatch(batchId: string | number): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/placement-batch/${batchId}`)
|
||||
return apiFetch(`${BASE}/placement/batch/${batchId}`)
|
||||
}
|
||||
|
||||
export async function postPlacementBatch(
|
||||
batchId: string | number,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/placement-batch/${batchId}`, {
|
||||
return apiFetch(`${BASE}/placement/batch/${batchId}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
@@ -295,11 +295,11 @@ export async function postPlacementBatch(
|
||||
export async function fetchScheduleMeetingForm(
|
||||
searchParams: URLSearchParams,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/schedule-meeting${q(searchParams)}`)
|
||||
return apiFetch(`${BASE}/below-sixty/meeting${q(searchParams)}`)
|
||||
}
|
||||
|
||||
export async function postScheduleMeeting(body: Record<string, unknown>): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/schedule-meeting`, {
|
||||
return apiFetch(`${BASE}/below-sixty/meeting`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
|
||||
+24
-15
@@ -11,6 +11,13 @@ function q(searchParams: URLSearchParams): string {
|
||||
return s ? `?${s}` : ''
|
||||
}
|
||||
|
||||
function unwrapData<T>(body: T | { data?: T }): T {
|
||||
if (body && typeof body === 'object' && 'data' in body && (body as { data?: T }).data != null) {
|
||||
return (body as { data?: T }).data as T
|
||||
}
|
||||
return body as T
|
||||
}
|
||||
|
||||
export type InventoryKind = 'book' | 'classroom' | 'kitchen' | 'office'
|
||||
|
||||
/** Index list: expect `{ items, categories, userNames }` (names optional). */
|
||||
@@ -20,11 +27,11 @@ export async function fetchInventoryIndex(
|
||||
): Promise<unknown> {
|
||||
const params = new URLSearchParams(searchParams)
|
||||
params.set('type', kind)
|
||||
return apiFetch(`${BASE}/items?${params.toString()}`)
|
||||
return unwrapData(await apiFetch(`${BASE}/items?${params.toString()}`))
|
||||
}
|
||||
|
||||
export async function postInventoryCategory(body: Record<string, unknown>): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/category`, {
|
||||
return apiFetch(`${BASE}/categories`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
@@ -37,7 +44,7 @@ export async function fetchInventoryItem(itemId: string | number): Promise<{
|
||||
conditionOptions?: Record<string, string>
|
||||
[key: string]: unknown
|
||||
}> {
|
||||
return apiFetch(`${BASE}/items/${itemId}`)
|
||||
return unwrapData(await apiFetch(`${BASE}/items/${itemId}`))
|
||||
}
|
||||
|
||||
/** Create form payload (categories, defaults). */
|
||||
@@ -45,7 +52,9 @@ export async function fetchInventoryCreateForm(
|
||||
kind: InventoryKind,
|
||||
searchParams: URLSearchParams,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/${kind}/create${q(searchParams)}`)
|
||||
const params = new URLSearchParams(searchParams)
|
||||
params.set('type', kind)
|
||||
return unwrapData(await apiFetch(`${BASE}/items?${params.toString()}`))
|
||||
}
|
||||
|
||||
export async function postInventoryItem(body: Record<string, unknown>): Promise<unknown> {
|
||||
@@ -61,7 +70,7 @@ export async function putInventoryItem(
|
||||
body: Record<string, unknown>,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/items/${itemId}`, {
|
||||
method: 'PUT',
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
@@ -86,7 +95,7 @@ export async function postClassroomAudit(
|
||||
itemId: string | number,
|
||||
body: Record<string, unknown>,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/items/${itemId}/classroom-audit`, {
|
||||
return apiFetch(`${BASE}/items/${itemId}/audit`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
@@ -98,11 +107,11 @@ export async function fetchInventorySummary(searchParams: URLSearchParams): Prom
|
||||
const params = new URLSearchParams(searchParams)
|
||||
params.delete('type')
|
||||
const qs = params.toString()
|
||||
return apiFetch(`${BASE}/summary/${type}${qs ? `?${qs}` : ''}`)
|
||||
return unwrapData(await apiFetch(`${BASE}/summary/${type}${qs ? `?${qs}` : ''}`))
|
||||
}
|
||||
|
||||
export async function fetchMovements(searchParams: URLSearchParams): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/movements${q(searchParams)}`)
|
||||
return unwrapData(await apiFetch(`${BASE}/movements${q(searchParams)}`))
|
||||
}
|
||||
|
||||
export async function fetchMovementForm(
|
||||
@@ -110,9 +119,9 @@ export async function fetchMovementForm(
|
||||
searchParams: URLSearchParams,
|
||||
): Promise<unknown> {
|
||||
if (movementId === undefined || movementId === '') {
|
||||
return apiFetch(`${BASE}/movements/create${q(searchParams)}`)
|
||||
return unwrapData(await apiFetch(`${BASE}/movements${q(searchParams)}`))
|
||||
}
|
||||
return apiFetch(`${BASE}/movements/${movementId}/edit${q(searchParams)}`)
|
||||
return unwrapData(await apiFetch(`${BASE}/movements/${movementId}/edit${q(searchParams)}`))
|
||||
}
|
||||
|
||||
export async function postMovement(body: Record<string, unknown>): Promise<unknown> {
|
||||
@@ -128,7 +137,7 @@ export async function putMovement(
|
||||
body: Record<string, unknown>,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/movements/${movementId}`, {
|
||||
method: 'PUT',
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
@@ -167,26 +176,26 @@ export async function postTeacherBookDistribute(body: Record<string, unknown>):
|
||||
|
||||
/** Fetch low-stock items (quantity <= reorder_point). */
|
||||
export async function fetchLowStock(): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/low-stock`)
|
||||
return unwrapData(await apiFetch(`${BASE}/low-stock`))
|
||||
}
|
||||
|
||||
/** Fetch reorder suggestions for low-stock items. */
|
||||
export async function fetchReorderSuggestions(): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/reorder-suggestions`)
|
||||
return unwrapData(await apiFetch(`${BASE}/reorder-suggestions`))
|
||||
}
|
||||
|
||||
/** Fetch stock status for all items (ok/low_stock/out_of_stock). */
|
||||
export async function fetchStockStatus(
|
||||
searchParams: URLSearchParams,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/stock-status${q(searchParams)}`)
|
||||
return unwrapData(await apiFetch(`${BASE}/stock-status${q(searchParams)}`))
|
||||
}
|
||||
|
||||
/** Fetch inventory dashboard summary counts. */
|
||||
export async function fetchInventoryDashboard(
|
||||
searchParams: URLSearchParams,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`${BASE}/dashboard${q(searchParams)}`)
|
||||
return unwrapData(await apiFetch(`${BASE}/dashboard${q(searchParams)}`))
|
||||
}
|
||||
|
||||
/** Void a movement (append-only correction with reverse entry). */
|
||||
|
||||
@@ -6,6 +6,13 @@ import { ApiHttpError, apiFetch, applyStoredSchoolYearHeaders, getStoredToken }
|
||||
|
||||
export type PrintRequestRow = Record<string, unknown>
|
||||
|
||||
function unwrapData<T>(body: T | { data?: T }): T {
|
||||
if (body && typeof body === 'object' && 'data' in body && (body as { data?: T }).data != null) {
|
||||
return (body as { data?: T }).data as T
|
||||
}
|
||||
return body as T
|
||||
}
|
||||
|
||||
export type TeacherPrintRequestsContext = {
|
||||
sundays?: string[]
|
||||
times?: string[]
|
||||
@@ -61,15 +68,15 @@ export async function deletePrintRequest(requestId: number | string): Promise<{
|
||||
}
|
||||
|
||||
export async function fetchAdminPrintRequests(): Promise<{ print_requests: PrintRequestRow[] }> {
|
||||
return apiFetch('/api/v1/administrator/print-requests')
|
||||
return unwrapData(await apiFetch('/api/v1/print-requests/admin'))
|
||||
}
|
||||
|
||||
export async function adminUpdatePrintRequestStatus(
|
||||
requestId: number | string,
|
||||
payload: { status: string; admin_id?: number | string },
|
||||
): Promise<{ ok?: boolean; message?: string }> {
|
||||
return apiFetch(`/api/v1/administrator/print-requests/${encodeURIComponent(String(requestId))}/status`, {
|
||||
method: 'POST',
|
||||
return apiFetch(`/api/v1/print-requests/${encodeURIComponent(String(requestId))}/status`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
|
||||
+29
-11
@@ -25,15 +25,23 @@ export async function fetchStickerFormOptions(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/printables/stickers/form-options${suffix}`)
|
||||
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 body = await apiFetch<{ students?: StudentOption[] } | StudentOption[]>(
|
||||
`/api/v1/administrator/students/by-class/${encodeURIComponent(String(classId))}`,
|
||||
)
|
||||
if (Array.isArray(body)) return body
|
||||
return body.students ?? []
|
||||
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> {
|
||||
@@ -41,7 +49,7 @@ export async function postStickerGenerate(formData: FormData): Promise<Blob> {
|
||||
const token = getStoredToken()
|
||||
if (token) headers.set('Authorization', `Bearer ${token}`)
|
||||
applyStoredSchoolYearHeaders(headers)
|
||||
const res = await fetch(apiUrl('/api/v1/administrator/printables/stickers/generate'), {
|
||||
const res = await fetch(apiUrl('/api/v1/reports/stickers/print'), {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: formData,
|
||||
@@ -91,7 +99,7 @@ export async function fetchStickerPreviewCounts(classId?: number | string | null
|
||||
const suffix = qs.toString() ? `?${qs}` : ''
|
||||
const body = await apiFetch<
|
||||
StickerPreviewCounts & { status?: string; data?: StickerPreviewCounts }
|
||||
>(`/api/v1/administrator/printables/stickers/preview${suffix}`)
|
||||
>(`/api/v1/reports/stickers/preview${suffix}`)
|
||||
if (body.data) return body.data
|
||||
return body
|
||||
}
|
||||
@@ -172,7 +180,8 @@ export async function fetchReportCardMeta(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/printables/report-card/meta${suffix}`)
|
||||
const body = await apiFetch<ReportCardMeta & { data?: ReportCardMeta }>(`/api/v1/administrator/printables/report-card/meta${suffix}`)
|
||||
return body.data ?? body
|
||||
}
|
||||
|
||||
export async function fetchReportCardAck(params: {
|
||||
@@ -184,9 +193,11 @@ export async function fetchReportCardAck(params: {
|
||||
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 ?? '')
|
||||
return apiFetch(`/api/v1/administrator/printables/report-card/ack?${qs}`)
|
||||
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
|
||||
@@ -201,9 +212,16 @@ export async function fetchReportCardCompleteness(params: {
|
||||
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 ?? '')
|
||||
return apiFetch(`/api/v1/administrator/printables/report-card/completeness?${qs}`)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
/** Build absolute API URL for iframe / window.open (report PDF HTML). */
|
||||
export function reportCardStudentUrl(
|
||||
studentId: number | string,
|
||||
|
||||
@@ -67,7 +67,7 @@ export async function downloadReimbursementsExport(params?: {
|
||||
|
||||
export async function downloadBatchCsv(batchId: number | string): Promise<void> {
|
||||
const qs = new URLSearchParams({ batch_id: String(batchId) })
|
||||
const url = apiUrl(`${BASE}/batch/export?${qs}`)
|
||||
const url = apiUrl(`${BASE}/batches/export?${qs}`)
|
||||
const headers = new Headers({ Accept: 'text/csv,*/*' })
|
||||
const token = getStoredToken()
|
||||
if (token) headers.set('Authorization', `Bearer ${token}`)
|
||||
@@ -83,7 +83,7 @@ export async function postBatchEmail(formData: FormData): Promise<{ success?: bo
|
||||
const token = getStoredToken()
|
||||
if (token) headers.set('Authorization', `Bearer ${token}`)
|
||||
applyStoredSchoolYearHeaders(headers)
|
||||
const res = await fetch(apiUrl(`${BASE}/batch/send`), { method: 'POST', headers, body: formData })
|
||||
const res = await fetch(apiUrl(`${BASE}/batches/email`), { method: 'POST', headers, body: formData })
|
||||
const body: unknown = await res.json().catch(() => null)
|
||||
if (!res.ok) {
|
||||
const msg =
|
||||
@@ -114,7 +114,7 @@ export async function createReimbursementBatch(): Promise<{
|
||||
csrf_hash?: string
|
||||
}> {
|
||||
const fd = new FormData()
|
||||
return postFormExpectJson(`${BASE}/batch/create`, fd)
|
||||
return postFormExpectJson(`${BASE}/batches`, fd)
|
||||
}
|
||||
|
||||
async function postFormExpectJson(path: string, form: FormData): Promise<{ success?: boolean; error?: string; csrf_hash?: string; reimbursement_id?: number } & Record<string, unknown>> {
|
||||
@@ -135,15 +135,15 @@ async function postFormExpectJson(path: string, form: FormData): Promise<{ succe
|
||||
}
|
||||
|
||||
export function postBatchUpdate(form: FormData) {
|
||||
return postFormExpectJson(`${BASE}/batch/update`, form)
|
||||
return postFormExpectJson(`${BASE}/batches/assign`, form)
|
||||
}
|
||||
|
||||
export function postBatchLock(form: FormData) {
|
||||
return postFormExpectJson(`${BASE}/batch/lock`, form)
|
||||
return postFormExpectJson(`${BASE}/batches/lock`, form)
|
||||
}
|
||||
|
||||
export function postAdminCheckUpload(form: FormData) {
|
||||
return postFormExpectJson(`${BASE}/batch/admin-file/upload`, form)
|
||||
return postFormExpectJson(`${BASE}/batches/admin-files`, form)
|
||||
}
|
||||
|
||||
export function postMarkDonation(form: FormData) {
|
||||
|
||||
+14
-2
@@ -32,6 +32,7 @@ import type {
|
||||
NavMenuResponse,
|
||||
ParentAttendanceResponse,
|
||||
ParentAttendanceReportFormResponse,
|
||||
ParentEnrollmentActionResponse,
|
||||
ParentEnrollmentsResponse,
|
||||
ParentEventsOverviewResponse,
|
||||
ParentInvoicesResponse,
|
||||
@@ -1784,7 +1785,7 @@ export async function fetchPaypalTransactions(params?: {
|
||||
if (params?.q) query.set('q', params.q)
|
||||
if (params?.perPage) query.set('per_page', String(params.perPage))
|
||||
const qs = query.size > 0 ? `?${query.toString()}` : ''
|
||||
return apiFetch<PaypalTransactionsResponse>(`/api/v1/paypal-transactions${qs}`)
|
||||
return apiFetch<PaypalTransactionsResponse>(`/api/v1/administrator/paypal-transactions${qs}`)
|
||||
}
|
||||
|
||||
export async function fetchPaypalTransactionsCsv(params?: { q?: string }): Promise<Blob> {
|
||||
@@ -1795,7 +1796,7 @@ export async function fetchPaypalTransactionsCsv(params?: { q?: string }): Promi
|
||||
const query = new URLSearchParams()
|
||||
if (params?.q) query.set('q', params.q)
|
||||
const suffix = query.size > 0 ? `?${query.toString()}` : ''
|
||||
const res = await fetch(apiUrl(`/api/v1/paypal-transactions/csv${suffix}`), { headers })
|
||||
const res = await fetch(apiUrl(`/api/v1/administrator/paypal-transactions/csv${suffix}`), { headers })
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
return res.blob()
|
||||
}
|
||||
@@ -1858,6 +1859,17 @@ export async function fetchParentEnrollments(
|
||||
return apiFetch<ParentEnrollmentsResponse>(`/api/v1/parents/enrollments${q}`)
|
||||
}
|
||||
|
||||
export async function updateParentEnrollments(payload: {
|
||||
enroll: number[]
|
||||
withdraw: number[]
|
||||
}): Promise<ParentEnrollmentActionResponse> {
|
||||
return apiFetch<ParentEnrollmentActionResponse>('/api/v1/parents/enrollments', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
}
|
||||
|
||||
export async function fetchParentInvoices(
|
||||
schoolYear: string | null,
|
||||
): Promise<ParentInvoicesResponse> {
|
||||
|
||||
+10
-3
@@ -6,6 +6,13 @@ import { apiFetch } from './http'
|
||||
|
||||
const BASE = '/api/v1/administrator/staff'
|
||||
|
||||
function unwrapData<T>(body: T | { data?: T }): T {
|
||||
if (body && typeof body === 'object' && 'data' in body && (body as { data?: T }).data != null) {
|
||||
return (body as { data?: T }).data as T
|
||||
}
|
||||
return body as T
|
||||
}
|
||||
|
||||
export type StaffListRow = {
|
||||
id: number
|
||||
user_id?: number
|
||||
@@ -57,15 +64,15 @@ export async function fetchStaffIndex(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}${suffix}`)
|
||||
return unwrapData(await apiFetch(`${BASE}${suffix}`))
|
||||
}
|
||||
|
||||
export async function fetchStaffFormOptions(): Promise<StaffFormOptionsResponse> {
|
||||
return apiFetch(`${BASE}/form-options`)
|
||||
return unwrapData(await apiFetch(`${BASE}/form-options`))
|
||||
}
|
||||
|
||||
export async function fetchStaffMember(id: number): Promise<StaffMemberResponse> {
|
||||
return apiFetch(`${BASE}/${id}`)
|
||||
return unwrapData(await apiFetch(`${BASE}/${id}`))
|
||||
}
|
||||
|
||||
export async function createStaff(payload: {
|
||||
|
||||
@@ -377,6 +377,19 @@ export type ParentEnrollmentsResponse = {
|
||||
students: ParentEnrollmentStudent[]
|
||||
schoolYears: { school_year: string }[]
|
||||
selectedYear: string | null
|
||||
withdrawalDeadline?: string | null
|
||||
lastDayOfRegistration?: string | null
|
||||
schoolStartDate?: string | null
|
||||
}
|
||||
|
||||
export type ParentEnrollmentActionResponse = {
|
||||
ok: true
|
||||
enrolled: number[]
|
||||
withdrawn: number[]
|
||||
data?: {
|
||||
enrolled?: number[]
|
||||
withdrawn?: number[]
|
||||
}
|
||||
}
|
||||
|
||||
export type ContactSubmitResponse = {
|
||||
|
||||
+55
-9
@@ -6,6 +6,13 @@ import { apiFetch } from './http'
|
||||
|
||||
const BASE = '/api/v1/whatsapp'
|
||||
|
||||
function unwrapData<T>(body: T | { data?: T }): T {
|
||||
if (body && typeof body === 'object' && 'data' in body && (body as { data?: T }).data != null) {
|
||||
return (body as { data?: T }).data as T
|
||||
}
|
||||
return body as T
|
||||
}
|
||||
|
||||
export type WhatsappSectionRow = {
|
||||
class_section_id?: number
|
||||
id?: number
|
||||
@@ -16,6 +23,9 @@ export type WhatsappSectionRow = {
|
||||
}
|
||||
|
||||
export type WhatsappLinkRow = {
|
||||
id?: number
|
||||
class_section_id?: number
|
||||
class_section_name?: string
|
||||
invite_link?: string | null
|
||||
active?: number | boolean
|
||||
}
|
||||
@@ -79,8 +89,29 @@ export async function fetchWhatsappManageLinks(params?: {
|
||||
const qs = new URLSearchParams()
|
||||
if (params?.school_year) qs.set('school_year', params.school_year)
|
||||
if (params?.semester) qs.set('semester', params.semester)
|
||||
if (!qs.has('per_page')) qs.set('per_page', '200')
|
||||
const suffix = qs.toString() ? `?${qs}` : ''
|
||||
return apiFetch(`${BASE}/links${suffix}`)
|
||||
const body = await apiFetch<WhatsappManageLinksResponse & {
|
||||
data?: WhatsappManageLinksResponse & { links?: WhatsappLinkRow[] }
|
||||
links?: WhatsappLinkRow[]
|
||||
class_sections?: ParentContactsByClassSection[]
|
||||
}>(`${BASE}/links${suffix}`)
|
||||
const data = unwrapData(body) as WhatsappManageLinksResponse & {
|
||||
links?: WhatsappLinkRow[]
|
||||
class_sections?: ParentContactsByClassSection[]
|
||||
}
|
||||
|
||||
if (!data.sections && data.class_sections) data.sections = data.class_sections
|
||||
|
||||
if (!data.linksBySection && Array.isArray(data.links)) {
|
||||
data.linksBySection = Object.fromEntries(
|
||||
data.links
|
||||
.filter((link) => link.class_section_id != null)
|
||||
.map((link) => [String(link.class_section_id), link]),
|
||||
)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export async function saveWhatsappLink(payload: {
|
||||
@@ -89,11 +120,11 @@ export async function saveWhatsappLink(payload: {
|
||||
invite_link: string
|
||||
active?: boolean
|
||||
}): Promise<{ ok?: boolean; message?: string }> {
|
||||
return apiFetch(`${BASE}/links`, {
|
||||
return unwrapData(await apiFetch(`${BASE}/links`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
export async function sendWhatsappInvites(payload: {
|
||||
@@ -101,11 +132,11 @@ export async function sendWhatsappInvites(payload: {
|
||||
class_section_id?: number | null
|
||||
parent_ids?: number[]
|
||||
}): Promise<{ ok?: boolean; message?: string }> {
|
||||
return apiFetch(`${BASE}/invites/send`, {
|
||||
return unwrapData(await apiFetch(`${BASE}/invites/send`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
export async function fetchWhatsappParentContacts(params?: {
|
||||
@@ -116,11 +147,25 @@ export async function fetchWhatsappParentContacts(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}/parent-contacts${suffix}`)
|
||||
const body = await apiFetch<WhatsappParentContactsResponse & { data?: WhatsappParentContactsResponse }>(`${BASE}/parent-contacts${suffix}`)
|
||||
return unwrapData(body)
|
||||
}
|
||||
|
||||
export async function fetchWhatsappParentContactsByClass(): Promise<WhatsappParentContactsByClassResponse> {
|
||||
return apiFetch(`${BASE}/parent-contacts-by-class`)
|
||||
export async function fetchWhatsappParentContactsByClass(params?: {
|
||||
school_year?: string
|
||||
semester?: string
|
||||
}): Promise<WhatsappParentContactsByClassResponse> {
|
||||
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<{
|
||||
data?: { class_sections?: ParentContactsByClassSection[]; classSections?: ParentContactsByClassSection[] }
|
||||
class_sections?: ParentContactsByClassSection[]
|
||||
classSections?: ParentContactsByClassSection[]
|
||||
}>(`${BASE}/parent-contacts-by-class${suffix}`)
|
||||
const data = unwrapData(body)
|
||||
return { classSections: data.classSections ?? data.class_sections ?? [] }
|
||||
}
|
||||
|
||||
export async function updateWhatsappMembership(payload: {
|
||||
@@ -132,9 +177,10 @@ export async function updateWhatsappMembership(payload: {
|
||||
primary_member?: string
|
||||
second_member?: string
|
||||
}): Promise<{ status?: string; message?: string }> {
|
||||
return apiFetch(`${BASE}/membership`, {
|
||||
const body = await apiFetch<{ status?: boolean; message?: string; data?: unknown }>(`${BASE}/membership`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
return { status: body.status ? 'ok' : undefined, message: body.message }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user