feat: add absence notification and analytics features
- Implemented AbsenceNotifyView for notifying absence with session details. - Created AnalyticsView for displaying group analytics and managing students at risk. - Added ClassAttendanceView for marking attendance in classes. - Defined new schemas for analytics and attendance in shared package.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
export * from './lib/problem-details.js';
|
||||
export * from './lib/result.js';
|
||||
export * from './schemas/analytics.js';
|
||||
export * from './schemas/attendance.js';
|
||||
export * from './schemas/attendees.js';
|
||||
export * from './schemas/enums.js';
|
||||
export * from './schemas/groups.js';
|
||||
|
||||
67
packages/shared/src/schemas/analytics.ts
Normal file
67
packages/shared/src/schemas/analytics.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { z } from 'zod';
|
||||
import { attendanceStatusSchema } from './attendance.js';
|
||||
|
||||
const isoDateTimeSchema = z.string().datetime();
|
||||
|
||||
export const attendeeStatusSchema = z.enum(['ACTIVE', 'PAUSED', 'DROPPED']);
|
||||
export type AttendeeStatus = z.output<typeof attendeeStatusSchema>;
|
||||
|
||||
export const riskLevelSchema = z.enum(['HIGH', 'MEDIUM']);
|
||||
export type RiskLevel = z.output<typeof riskLevelSchema>;
|
||||
|
||||
export const GroupAnalyticsSchema = z.object({
|
||||
attendanceRate: z.number().min(0).max(100),
|
||||
totalPresent: z.number().int().min(0),
|
||||
totalClasses: z.number().int().min(0),
|
||||
recoveredSlots: z.number().int().min(0),
|
||||
});
|
||||
export type GroupAnalytics = z.output<typeof GroupAnalyticsSchema>;
|
||||
export type GroupAnalyticsDto = GroupAnalytics;
|
||||
|
||||
export const StudentAtRiskSchema = z.object({
|
||||
attendeeId: z.string(),
|
||||
fullName: z.string(),
|
||||
phone: z.string().nullable(),
|
||||
riskLevel: riskLevelSchema,
|
||||
consecutiveAbsences: z.number().int().min(0),
|
||||
lastAttendedAt: isoDateTimeSchema.nullable(),
|
||||
monthlyAttendanceRate: z.number().min(0).max(100),
|
||||
});
|
||||
export type StudentAtRisk = z.output<typeof StudentAtRiskSchema>;
|
||||
export type StudentAtRiskDto = StudentAtRisk;
|
||||
|
||||
export const StudentsAtRiskSchema = z.object({
|
||||
groupName: z.string(),
|
||||
data: z.array(StudentAtRiskSchema),
|
||||
});
|
||||
export type StudentsAtRisk = z.output<typeof StudentsAtRiskSchema>;
|
||||
export type StudentsAtRiskDto = StudentsAtRisk;
|
||||
|
||||
export const AttendanceHistoryItemSchema = z.object({
|
||||
classSessionId: z.string(),
|
||||
startsAt: isoDateTimeSchema,
|
||||
status: attendanceStatusSchema.nullable(),
|
||||
});
|
||||
export type AttendanceHistoryItem = z.output<typeof AttendanceHistoryItemSchema>;
|
||||
|
||||
export const AttendeeHistorySchema = z.object({
|
||||
attendeeId: z.string(),
|
||||
fullName: z.string(),
|
||||
groupId: z.string(),
|
||||
groupName: z.string(),
|
||||
attendanceRate: z.number().min(0).max(100),
|
||||
sessions: z.array(AttendanceHistoryItemSchema),
|
||||
});
|
||||
export type AttendeeHistory = z.output<typeof AttendeeHistorySchema>;
|
||||
export type AttendeeHistoryDto = AttendeeHistory;
|
||||
|
||||
export const UpdateStudentStatusSchema = z.object({
|
||||
status: attendeeStatusSchema,
|
||||
});
|
||||
export type UpdateStudentStatus = z.output<typeof UpdateStudentStatusSchema>;
|
||||
|
||||
export const UpdateStudentStatusResultSchema = z.object({
|
||||
attendeeId: z.string(),
|
||||
status: attendeeStatusSchema,
|
||||
});
|
||||
export type UpdateStudentStatusResult = z.output<typeof UpdateStudentStatusResultSchema>;
|
||||
111
packages/shared/src/schemas/attendance.ts
Normal file
111
packages/shared/src/schemas/attendance.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { z } from 'zod';
|
||||
import { timeZoneSchema } from './home.js';
|
||||
|
||||
const isoDateTimeSchema = z.string().datetime();
|
||||
|
||||
export const attendanceStatusSchema = z.enum(['PRESENT', 'ABSENT', 'EXCUSED']);
|
||||
export type AttendanceStatus = z.output<typeof attendanceStatusSchema>;
|
||||
|
||||
export const attendeePaymentStatusSchema = z.enum(['UP_TO_DATE', 'PENDING']);
|
||||
export type AttendeePaymentStatus = z.output<typeof attendeePaymentStatusSchema>;
|
||||
|
||||
export const MarkAttendanceRecordSchema = z.object({
|
||||
attendeeId: z.string().min(1),
|
||||
status: z.enum(['PRESENT', 'ABSENT']),
|
||||
});
|
||||
export type MarkAttendanceRecord = z.output<typeof MarkAttendanceRecordSchema>;
|
||||
|
||||
export const MarkAttendanceSchema = z.object({
|
||||
classSessionId: z.string().min(1),
|
||||
records: z.array(MarkAttendanceRecordSchema).min(1, 'Debes enviar al menos un registro de asistencia'),
|
||||
});
|
||||
export type MarkAttendance = z.output<typeof MarkAttendanceSchema>;
|
||||
|
||||
export const MarkAttendanceResultSchema = z.object({
|
||||
classSessionId: z.string(),
|
||||
marked: z.number().int().min(0),
|
||||
});
|
||||
export type MarkAttendanceResult = z.output<typeof MarkAttendanceResultSchema>;
|
||||
|
||||
export const NotifyAbsenceSchema = z.object({
|
||||
attendeeId: z.string().min(1),
|
||||
classSessionId: z.string().min(1),
|
||||
});
|
||||
export type NotifyAbsence = z.output<typeof NotifyAbsenceSchema>;
|
||||
|
||||
export const PublicNotifyAbsenceSchema = z.object({
|
||||
token: z.string().min(1),
|
||||
classSessionId: z.string().min(1),
|
||||
});
|
||||
export type PublicNotifyAbsence = z.output<typeof PublicNotifyAbsenceSchema>;
|
||||
|
||||
export const NotifyAbsenceResultSchema = z.object({
|
||||
classSessionId: z.string(),
|
||||
notified: z.boolean(),
|
||||
slotReleased: z.boolean(),
|
||||
});
|
||||
export type NotifyAbsenceResult = z.output<typeof NotifyAbsenceResultSchema>;
|
||||
|
||||
export const AbsenceQuerySchema = z.object({
|
||||
token: z.string().min(1),
|
||||
});
|
||||
export type AbsenceQuery = z.output<typeof AbsenceQuerySchema>;
|
||||
|
||||
export const ClassTodaySchema = z.object({
|
||||
sessionId: z.string(),
|
||||
groupId: z.string(),
|
||||
groupName: z.string(),
|
||||
startsAt: isoDateTimeSchema,
|
||||
enrolledCount: z.number().int().min(0),
|
||||
capacity: z.number().int().min(1).nullable(),
|
||||
availableSlots: z.number().int().min(0).nullable(),
|
||||
hasAttendance: z.boolean(),
|
||||
});
|
||||
export type ClassToday = z.output<typeof ClassTodaySchema>;
|
||||
export type ClassTodayDto = ClassToday;
|
||||
|
||||
export const ClassTodayListSchema = z.object({
|
||||
data: z.array(ClassTodaySchema),
|
||||
});
|
||||
export type ClassTodayList = z.output<typeof ClassTodayListSchema>;
|
||||
|
||||
export const TodayClassesQuerySchema = z.object({
|
||||
timeZone: timeZoneSchema.optional(),
|
||||
});
|
||||
export type TodayClassesQuery = z.output<typeof TodayClassesQuerySchema>;
|
||||
|
||||
export const SessionStudentSchema = z.object({
|
||||
attendeeId: z.string(),
|
||||
fullName: z.string(),
|
||||
paymentStatus: attendeePaymentStatusSchema,
|
||||
attendanceStatus: attendanceStatusSchema.nullable(),
|
||||
notifiedAbsence: z.boolean(),
|
||||
});
|
||||
export type SessionStudent = z.output<typeof SessionStudentSchema>;
|
||||
export type SessionStudentDto = SessionStudent;
|
||||
|
||||
export const SessionStudentsSchema = z.object({
|
||||
sessionId: z.string(),
|
||||
groupId: z.string(),
|
||||
groupName: z.string(),
|
||||
startsAt: isoDateTimeSchema,
|
||||
students: z.array(SessionStudentSchema),
|
||||
});
|
||||
export type SessionStudents = z.output<typeof SessionStudentsSchema>;
|
||||
|
||||
export const AbsenceSessionSchema = z.object({
|
||||
sessionId: z.string(),
|
||||
groupName: z.string(),
|
||||
startsAt: isoDateTimeSchema,
|
||||
notified: z.boolean(),
|
||||
attendanceStatus: attendanceStatusSchema.nullable(),
|
||||
});
|
||||
export type AbsenceSession = z.output<typeof AbsenceSessionSchema>;
|
||||
|
||||
export const AbsenceDetailsSchema = z.object({
|
||||
attendeeId: z.string(),
|
||||
fullName: z.string(),
|
||||
groupName: z.string(),
|
||||
sessions: z.array(AbsenceSessionSchema),
|
||||
});
|
||||
export type AbsenceDetails = z.output<typeof AbsenceDetailsSchema>;
|
||||
@@ -16,6 +16,7 @@ export const AttendeeDtoSchema = z.object({
|
||||
guardianName: z.string().nullable(),
|
||||
guardianPhone: z.string().nullable(),
|
||||
notes: z.string().nullable(),
|
||||
notifyToken: z.string().nullable().optional(),
|
||||
createdAt: isoDateTimeSchema,
|
||||
updatedAt: isoDateTimeSchema,
|
||||
});
|
||||
|
||||
@@ -15,6 +15,8 @@ const timeZoneSchema = z
|
||||
}
|
||||
}, 'Zona horaria IANA inválida');
|
||||
|
||||
export { timeZoneSchema };
|
||||
|
||||
export const HomeStatsSchema = z.object({
|
||||
groups: z.number().int().nonnegative(),
|
||||
attendees: z.number().int().nonnegative(),
|
||||
|
||||
Reference in New Issue
Block a user