feat(schedule): add course table screens and navigation

Add complete schedule functionality including:
- Schedule screen with weekly course table view
- Course detail screen with transparent modal presentation
- New ScheduleStack navigator integrated into main tab bar
- Schedule service for API interactions
- Type definitions for course entities

Also includes bug fixes for group invite/request handlers
to include required groupId parameter.
This commit is contained in:
2026-03-12 08:38:14 +08:00
parent 21293644b8
commit 0a0cbacbcc
25 changed files with 3050 additions and 260 deletions

View File

@@ -0,0 +1,140 @@
package handler
import (
"strconv"
"github.com/gin-gonic/gin"
"carrot_bbs/internal/pkg/response"
"carrot_bbs/internal/service"
)
type ScheduleHandler struct {
scheduleService service.ScheduleService
}
func NewScheduleHandler(scheduleService service.ScheduleService) *ScheduleHandler {
return &ScheduleHandler{scheduleService: scheduleService}
}
type createScheduleCourseRequest struct {
Name string `json:"name" binding:"required"`
Teacher string `json:"teacher"`
Location string `json:"location"`
DayOfWeek int `json:"day_of_week" binding:"required"`
StartSection int `json:"start_section" binding:"required"`
EndSection int `json:"end_section" binding:"required"`
Weeks []int `json:"weeks" binding:"required,min=1"`
Color string `json:"color"`
}
type updateScheduleCourseRequest = createScheduleCourseRequest
func (h *ScheduleHandler) ListCourses(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
week := 0
if rawWeek := c.Query("week"); rawWeek != "" {
parsed, err := strconv.Atoi(rawWeek)
if err != nil {
response.BadRequest(c, "invalid week")
return
}
week = parsed
}
list, err := h.scheduleService.ListCourses(userID, week)
if err != nil {
response.HandleError(c, err, "failed to list schedule courses")
return
}
response.Success(c, gin.H{"list": list})
}
func (h *ScheduleHandler) CreateCourse(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
var req createScheduleCourseRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
created, err := h.scheduleService.CreateCourse(userID, service.CreateScheduleCourseInput{
Name: req.Name,
Teacher: req.Teacher,
Location: req.Location,
DayOfWeek: req.DayOfWeek,
StartSection: req.StartSection,
EndSection: req.EndSection,
Weeks: req.Weeks,
Color: req.Color,
})
if err != nil {
response.HandleError(c, err, "failed to create schedule course")
return
}
response.SuccessWithMessage(c, "course created", gin.H{"course": created})
}
func (h *ScheduleHandler) UpdateCourse(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
courseID := c.Param("id")
if courseID == "" {
response.BadRequest(c, "invalid course id")
return
}
var req updateScheduleCourseRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
updated, err := h.scheduleService.UpdateCourse(userID, courseID, service.CreateScheduleCourseInput{
Name: req.Name,
Teacher: req.Teacher,
Location: req.Location,
DayOfWeek: req.DayOfWeek,
StartSection: req.StartSection,
EndSection: req.EndSection,
Weeks: req.Weeks,
Color: req.Color,
})
if err != nil {
response.HandleError(c, err, "failed to update schedule course")
return
}
response.SuccessWithMessage(c, "course updated", gin.H{"course": updated})
}
func (h *ScheduleHandler) DeleteCourse(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "")
return
}
courseID := c.Param("id")
if courseID == "" {
response.BadRequest(c, "invalid course id")
return
}
if err := h.scheduleService.DeleteCourse(userID, courseID); err != nil {
response.HandleError(c, err, "failed to delete schedule course")
return
}
response.SuccessWithMessage(c, "course deleted", nil)
}