fix attendance
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { apiFetch } from './http'
|
||||
|
||||
const BASE = '/api/v1/attendance/management'
|
||||
|
||||
type ApiEnvelope<T> = { status?: boolean; message?: string; data?: T }
|
||||
|
||||
function unwrap<T>(body: ApiEnvelope<T> | T): T {
|
||||
if (body && typeof body === 'object' && 'data' in body) return (body as ApiEnvelope<T>).data as T
|
||||
return body as T
|
||||
}
|
||||
|
||||
export type AttendanceManagementSummary = {
|
||||
present: number
|
||||
absent: number
|
||||
late: number
|
||||
early_dismissal: number
|
||||
not_reported: number
|
||||
follow_up_required: number
|
||||
badge_exceptions: number
|
||||
late_slip_reprints: number
|
||||
}
|
||||
|
||||
export type AttendanceManagementEvent = {
|
||||
id: number
|
||||
person_type: string
|
||||
person_id: number | null
|
||||
person_name: string | null
|
||||
role_grade: string | null
|
||||
badge_id: string | null
|
||||
event_date: string
|
||||
attendance_status: string
|
||||
report_status: string
|
||||
entry_time: string | null
|
||||
exit_time: string | null
|
||||
official_entry_time: string | null
|
||||
official_exit_time: string | null
|
||||
entry_method: string | null
|
||||
exit_method: string | null
|
||||
manual_reason: string | null
|
||||
reason: string | null
|
||||
scan_location: string | null
|
||||
exit_location: string | null
|
||||
absence_count: number
|
||||
late_count: number
|
||||
early_dismissal_count: number
|
||||
badge_exception_count: number
|
||||
combination_code: string | null
|
||||
risk_level: string
|
||||
follow_up_required: boolean | number
|
||||
follow_up_completed: boolean | number
|
||||
action_needed: string | null
|
||||
final_decision: string | null
|
||||
notes: string | null
|
||||
}
|
||||
|
||||
export type AttendanceManagementDashboard = {
|
||||
date: string
|
||||
summary: AttendanceManagementSummary
|
||||
filters: {
|
||||
attendance_status: string[]
|
||||
report_status: string[]
|
||||
risk_level: string[]
|
||||
person_type: string[]
|
||||
}
|
||||
rows: AttendanceManagementEvent[]
|
||||
}
|
||||
|
||||
export type ManualEntryPayload = {
|
||||
person_type?: string
|
||||
person_id?: number
|
||||
person_name?: string
|
||||
role_grade?: string
|
||||
badge_id?: string
|
||||
entry_time?: string
|
||||
manual_reason?: string
|
||||
report_status?: string
|
||||
reason?: string
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export type ScanPayload = {
|
||||
badge_scan?: string
|
||||
badge_id?: string
|
||||
scan_type?: 'entry' | 'exit'
|
||||
scan_time?: string
|
||||
location?: string
|
||||
report_status?: string
|
||||
authorized?: boolean
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export async function fetchAttendanceManagementDashboard(params: Record<string, string> = {}) {
|
||||
const qs = new URLSearchParams()
|
||||
Object.entries(params).forEach(([k, v]) => {
|
||||
if (v.trim()) qs.set(k, v.trim())
|
||||
})
|
||||
const suffix = qs.toString() ? `?${qs.toString()}` : ''
|
||||
return unwrap<AttendanceManagementDashboard>(await apiFetch(`${BASE}/dashboard${suffix}`))
|
||||
}
|
||||
|
||||
export async function fetchBadgeScanningList(params: Record<string, string> = {}) {
|
||||
return fetchAttendanceManagementDashboard({
|
||||
...params,
|
||||
entry_method: 'badge_scan',
|
||||
})
|
||||
}
|
||||
|
||||
export async function recordManualAttendanceEntry(payload: ManualEntryPayload) {
|
||||
return unwrap<{ event: AttendanceManagementEvent }>(
|
||||
await apiFetch(`${BASE}/manual-entry`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export async function recordAttendanceBadgeScan(payload: ScanPayload) {
|
||||
return unwrap<{ event: AttendanceManagementEvent }>(
|
||||
await apiFetch(`${BASE}/scan`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export async function completeAttendanceFollowUp(id: number, payload: { report_status?: string; final_decision?: string; notes?: string }) {
|
||||
return unwrap<{ event: AttendanceManagementEvent }>(
|
||||
await apiFetch(`${BASE}/${id}/follow-up`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export async function reprintAttendanceLateSlip(id: number, payload: { reason?: string; slip_number?: string } = {}) {
|
||||
return unwrap<{ reprint: Record<string, unknown> }>(
|
||||
await apiFetch(`${BASE}/${id}/late-slip-reprint`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
)
|
||||
}
|
||||
+3
-2
@@ -381,7 +381,8 @@ 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${qs}`)
|
||||
const raw = await apiFetch<unknown>(`/api/v1/attendance/staff/month${qs}`)
|
||||
return unwrapApiDataLayer(raw) as StaffMonthlyOverviewPayload
|
||||
}
|
||||
|
||||
/** Matches CI `teacher_attendance_month` save cell (form body). */
|
||||
@@ -392,7 +393,7 @@ export async function saveStaffMonthlyAttendanceCell(payload: Record<string, str
|
||||
}> {
|
||||
const body = new URLSearchParams()
|
||||
for (const [k, v] of Object.entries(payload)) body.set(k, v)
|
||||
return apiFetch('/api/v1/attendance/staff/month-save', {
|
||||
return apiFetch('/api/v1/attendance/staff/cell', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: body.toString(),
|
||||
|
||||
Reference in New Issue
Block a user