add badge logic

This commit is contained in:
root
2026-04-25 01:23:20 -04:00
parent 3e77fc92c7
commit 93b75b9b3f
10 changed files with 717 additions and 108 deletions
+32 -5
View File
@@ -60,7 +60,7 @@ export async function postBadgeGenerate(formData: FormData): 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/administrator/printables/badges/generate'), {
const res = await fetch(apiUrl('/api/v1/badges/pdf'), {
method: 'POST',
headers,
body: formData,
@@ -98,18 +98,44 @@ export async function fetchBadgeFormOptions(params?: {
school_year?: string
semester?: string
}): Promise<{
active_role?: string
rolesTabs?: Record<string, string>
schoolYears?: string[]
selectedYear?: string
selectedStudentIds?: number[]
selectedUserIds?: number[]
users?: BadgeUserRow[]
}> {
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}` : ''
return apiFetch(`/api/v1/administrator/printables/badges/form-options${suffix}`)
const body = await apiFetch<{
ok?: boolean
data?: {
active_role?: string
rolesTabs?: Record<string, string>
schoolYears?: string[]
selectedYear?: string
selectedStudentIds?: number[]
selectedUserIds?: number[]
users?: BadgeUserRow[]
}
} & {
active_role?: string
rolesTabs?: Record<string, string>
schoolYears?: string[]
selectedYear?: string
selectedStudentIds?: number[]
selectedUserIds?: number[]
users?: BadgeUserRow[]
}>(`/api/v1/administrator/printables/badges/form-options${suffix}`)
return body.data ?? body
}
export async function fetchBadgePrintStatus(params: {
user_ids: (number | string)[]
user_ids?: (number | string)[]
student_ids?: (number | string)[]
school_year?: string
}): Promise<{
ok?: boolean
@@ -118,9 +144,10 @@ export async function fetchBadgePrintStatus(params: {
csrf_hash?: string
}> {
const qs = new URLSearchParams()
for (const id of params.user_ids) qs.append('user_ids[]', String(id))
for (const id of params.user_ids ?? []) qs.append('user_ids[]', String(id))
for (const id of params.student_ids ?? []) qs.append('student_ids[]', String(id))
if (params.school_year) qs.set('school_year', params.school_year)
return apiFetch(`/api/v1/administrator/printables/badges/status?${qs}`)
return apiFetch(`/api/v1/badges/print-status?${qs}`)
}
export type ReportCardMeta = {
+89 -11
View File
@@ -2,7 +2,7 @@
* Roles & permissions admin (legacy CI rolepermission/*).
* Expected under `/api/v1/role-permission`.
*/
import { apiFetch } from './http'
import { ApiHttpError, apiFetch } from './http'
const BASE = '/api/v1/role-permission'
@@ -31,11 +31,64 @@ export type PermissionRow = {
updated_at?: string
}
export async function fetchRolesList(): Promise<{ roles?: RoleRow[] }> {
const body = await apiFetch<{ roles?: RoleRow[] } | { data?: { roles?: RoleRow[] } }>(`${BASE}/roles`)
export async function fetchRolesList(params?: {
page?: number
per_page?: number
sort_by?: string
sort_dir?: 'asc' | 'desc'
}): Promise<{
roles?: RoleRow[]
meta?: { current_page?: number; last_page?: number; total?: number; per_page?: number }
}> {
const qs = new URLSearchParams()
qs.set('per_page', String(params?.per_page ?? 200))
if (params?.page) qs.set('page', String(params.page))
if (params?.sort_by) qs.set('sort_by', params.sort_by)
if (params?.sort_dir) qs.set('sort_dir', params.sort_dir)
const suffix = qs.toString() ? `?${qs}` : ''
const body = await apiFetch<
| {
roles?: RoleRow[]
meta?: { current_page?: number; last_page?: number; total?: number; per_page?: number }
}
| {
data?: {
roles?: RoleRow[]
meta?: { current_page?: number; last_page?: number; total?: number; per_page?: number }
}
}
>(`${BASE}/roles${suffix}`)
return unwrapData(body)
}
export async function fetchAllRoles(): Promise<{ roles: RoleRow[] }> {
const rolesById = new Map<string, RoleRow>()
let page = 1
let lastPage = 1
do {
const response = await fetchRolesList({
page,
per_page: 200,
sort_by: 'name',
sort_dir: 'asc',
})
for (const role of response.roles ?? []) {
const key = String(role.id ?? '').trim()
if (key === '') {
continue
}
if (!rolesById.has(key)) {
rolesById.set(key, role)
}
}
lastPage = Number(response.meta?.last_page ?? page)
page += 1
} while (page <= lastPage)
return { roles: [...rolesById.values()] }
}
export async function fetchPermissionsList(): Promise<{ permissions?: PermissionRow[] }> {
const body = await apiFetch<
{ permissions?: PermissionRow[] } | { data?: { permissions?: PermissionRow[] } }
@@ -146,15 +199,34 @@ export async function updateRole(
roleId: number | string,
payload: Record<string, unknown>,
): Promise<{ ok?: boolean; message?: string }> {
const body = await apiFetch<{ ok?: boolean; message?: string } | { data?: { ok?: boolean; message?: string } }>(
`${BASE}/roles/${encodeURIComponent(String(roleId))}`,
{
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
try {
const body = await apiFetch<
{ ok?: boolean; message?: string } | { data?: { ok?: boolean; message?: string } }
>(`${BASE}/roles/${encodeURIComponent(String(roleId))}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
},
)
return unwrapData(body)
})
return unwrapData(body)
} catch (error) {
if (error instanceof ApiHttpError && error.status === 422) {
const body = error.body
if (body && typeof body === 'object') {
const errors = (body as { errors?: unknown }).errors
if (errors && typeof errors === 'object') {
for (const value of Object.values(errors as Record<string, unknown>)) {
if (Array.isArray(value) && value.length > 0) {
throw new Error(String(value[0]))
}
if (typeof value === 'string' && value.trim() !== '') {
throw new Error(value)
}
}
}
}
}
throw error
}
}
export async function deleteRole(roleId: number | string): Promise<{ ok?: boolean }> {
@@ -213,7 +285,13 @@ export async function updatePermission(
export type PermissionMatrixRow = {
id?: number | string
permission_id?: number | string
name?: string
description?: string
can_create?: boolean | number
can_read?: boolean | number
can_update?: boolean | number
can_delete?: boolean | number
}
export type ExistingPermissionFlags = {
+1 -1
View File
@@ -269,7 +269,7 @@ export async function fetchStaffMonthlyAttendanceOverview(params?: {
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<StaffMonthlyOverviewPayload>(`/api/v1/attendance/staff/month-overview${qs}`)
return apiFetch<StaffMonthlyOverviewPayload>(`/api/v1/attendance/staff/month${qs}`)
}
/** Matches CI `teacher_attendance_month` save cell (form body). */
+3
View File
@@ -257,9 +257,12 @@ export type StaffMonthlyOverviewPayload = {
school_year?: string
range_label?: string
month_label?: string
range_start?: string
range_end?: string
}
sundays?: string[]
noSchoolDays?: string[]
no_school_days?: string[]
sections?: StaffMonthlySection[]
admins?: StaffMonthlyAdminRow[]
missingYear?: boolean