package handlers import ( "fmt" "strconv" "strings" "time" internalDb "server/internal/db" "server/internal/models" "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) type studentShiftItem struct { Period int `json:"period"` CourseName string `json:"courseName"` StartTime string `json:"startTime"` EndTime string `json:"endTime"` IsActiveNow bool `json:"isActiveNow"` OnlineSeconds int `json:"onlineSeconds"` OfflineSeconds int `json:"offlineSeconds"` AttendanceStatus int `json:"attendanceStatus"` AttendanceLabel string `json:"attendanceLabel"` } // GET /api/student/status — trạng thái giám sát, lớp/ca hiện tại, tích lũy & điểm danh hôm nay func GetStudentStatusHandler(db *gorm.DB) fiber.Handler { return func(c *fiber.Ctx) error { studentID, err := strconv.ParseInt(c.Query("studentRkId", "0"), 10, 64) if err != nil || studentID <= 0 { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "studentRkId is required"}) } now := time.Now() today := now.Format("2006-01-02") dayIdx := weekdayIndex(now) currMin := now.Hour()*60 + now.Minute() classID, activePeriod := internalDb.FindActiveClassAndPeriodForStudent(db, studentID) if classID <= 0 { var link models.ClassStudent if err := db.Where("student_rk_id = ?", studentID).First(&link).Error; err == nil { classID = link.ClassRkID } } className, classCode := "", "" if classID > 0 { var cl models.Class if err := db.Where("rk_id = ?", classID).First(&cl).Error; err == nil { className = cl.Name classCode = cl.ClassCode } } var todaySchedules []models.ClassSchedule if classID > 0 { _ = db.Where("class_rk_id = ? AND day_of_week = ? AND is_active = ?", classID, dayIdx, true). Order("period asc, start_time asc").Find(&todaySchedules).Error } inScheduleNow := false currentPeriod := 0 currentCourse, currentStart, currentEnd := "", "", "" for _, s := range todaySchedules { startMin := parseTimeMinutes(s.StartTime) endMin := parseTimeMinutes(s.EndTime) if currMin >= startMin && currMin <= endMin { inScheduleNow = true currentPeriod = s.Period if currentPeriod < 1 { currentPeriod = 1 } currentCourse = s.CourseName currentStart = s.StartTime currentEnd = s.EndTime break } } if !inScheduleNow && activePeriod > 0 { currentPeriod = activePeriod } hasApps := false keywords := "" if classID > 0 { var appConfig models.ClassAllowedApp if err := db.Where("class_rk_id = ?", classID).First(&appConfig).Error; err == nil { keywords = strings.TrimSpace(appConfig.Keywords) hasApps = keywords != "" } } monitorMode := "outside_schedule" monitorLabel := "Ngoài giờ học" if classID <= 0 { monitorMode = "not_configured" monitorLabel = "Chưa xác định được lớp học" } else if !hasApps { monitorMode = "not_configured" monitorLabel = "Lớp chưa cấu hình ứng dụng được phép" } else if inScheduleNow { monitorMode = "learning" if currentCourse != "" { monitorLabel = fmt.Sprintf("Ca %d — %s (%s–%s)", currentPeriod, currentCourse, currentStart, currentEnd) } else { monitorLabel = fmt.Sprintf("Đang giám sát — Ca %d", currentPeriod) } } // monitorMode = "exam" — dành cho phòng thi (sẽ bổ sung sau) sessionMap := map[int]models.StudentSession{} attMap := map[int]models.AttendanceResult{} if classID > 0 { var sessions []models.StudentSession _ = db.Where("student_rk_id = ? AND class_rk_id = ? AND session_date = ?", studentID, classID, today).Find(&sessions).Error for _, s := range sessions { p := s.Period if p < 1 { p = 1 } sessionMap[p] = s } var attendances []models.AttendanceResult _ = db.Where("student_rk_id = ? AND class_rk_id = ? AND session_date = ?", studentID, classID, today).Find(&attendances).Error for _, a := range attendances { p := a.Period if p < 1 { p = 1 } attMap[p] = a } } shifts := make([]studentShiftItem, 0, len(todaySchedules)) for _, sch := range todaySchedules { period := sch.Period if period < 1 { period = 1 } sess := sessionMap[period] att := attMap[period] startMin := parseTimeMinutes(sch.StartTime) endMin := parseTimeMinutes(sch.EndTime) isActiveNow := currMin >= startMin && currMin <= endMin attStatus := -1 attLabel := "Chưa tính" if att.ID > 0 { attStatus = att.Status attLabel = att.StatusLabel if attLabel == "" { attLabel = attendanceLabelFallback(att.Status) } } shifts = append(shifts, studentShiftItem{ Period: period, CourseName: sch.CourseName, StartTime: sch.StartTime, EndTime: sch.EndTime, IsActiveNow: isActiveNow, OnlineSeconds: sess.OnlineSeconds, OfflineSeconds: sess.OfflineSeconds, AttendanceStatus: attStatus, AttendanceLabel: attLabel, }) } return c.JSON(fiber.Map{ "sessionDate": today, "monitorMode": monitorMode, "monitorLabel": monitorLabel, "classRkId": classID, "className": className, "classCode": classCode, "currentPeriod": currentPeriod, "currentCourseName": currentCourse, "currentShiftStart": currentStart, "currentShiftEnd": currentEnd, "inScheduleNow": inScheduleNow, "blockerActive": hasApps && inScheduleNow, "allowedKeywords": keywords, "shifts": shifts, }) } } func weekdayIndex(t time.Time) int { wd := t.Weekday() if wd == time.Sunday { return 6 } return int(wd) - 1 } func attendanceLabelFallback(status int) string { switch status { case 1: return "Nghỉ có phép" case 2: return "Nghỉ nửa buổi" case 3: return "Đi học muộn" case 4: return "Đi học đầy đủ" default: return "Nghỉ không phép" } }