Add timezone-aware overdue task detection
Fix issue where tasks showed as "Overdue" on the server while displaying "Tomorrow" on the client due to timezone differences between server (UTC) and user's local timezone. Changes: - Add X-Timezone header support to extract user's timezone from requests - Add TimezoneMiddleware to parse timezone and calculate user's local "today" - Update task categorization to accept custom time for accurate date comparisons - Update repository, service, and handler layers to pass timezone-aware time - Update CORS to allow X-Timezone header The client now sends the user's IANA timezone (e.g., "America/Los_Angeles") and the server uses it to determine if a task is overdue based on the user's local date, not UTC. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -55,12 +55,13 @@ func (s *TaskService) SetResidenceService(rs *ResidenceService) {
|
||||
s.residenceService = rs
|
||||
}
|
||||
|
||||
// getSummaryForUser gets the total summary for a user (helper for CRUD responses)
|
||||
// getSummaryForUser gets the total summary for a user (helper for CRUD responses).
|
||||
// Uses UTC time. For timezone-aware summary, call residence service directly.
|
||||
func (s *TaskService) getSummaryForUser(userID uint) responses.TotalSummary {
|
||||
if s.residenceService == nil {
|
||||
return responses.TotalSummary{}
|
||||
}
|
||||
summary, err := s.residenceService.GetSummary(userID)
|
||||
summary, err := s.residenceService.GetSummary(userID, time.Now().UTC())
|
||||
if err != nil || summary == nil {
|
||||
return responses.TotalSummary{}
|
||||
}
|
||||
@@ -92,8 +93,9 @@ func (s *TaskService) GetTask(taskID, userID uint) (*responses.TaskResponse, err
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// ListTasks lists all tasks accessible to a user as a kanban board
|
||||
func (s *TaskService) ListTasks(userID uint) (*responses.KanbanBoardResponse, error) {
|
||||
// ListTasks lists all tasks accessible to a user as a kanban board.
|
||||
// The `now` parameter should be the start of day in the user's timezone for accurate overdue detection.
|
||||
func (s *TaskService) ListTasks(userID uint, now time.Time) (*responses.KanbanBoardResponse, error) {
|
||||
// Get all residence IDs accessible to user
|
||||
residences, err := s.residenceRepo.FindByUser(userID)
|
||||
if err != nil {
|
||||
@@ -114,8 +116,8 @@ func (s *TaskService) ListTasks(userID uint) (*responses.KanbanBoardResponse, er
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get kanban data aggregated across all residences
|
||||
board, err := s.taskRepo.GetKanbanDataForMultipleResidences(residenceIDs, 30)
|
||||
// Get kanban data aggregated across all residences using user's timezone-aware time
|
||||
board, err := s.taskRepo.GetKanbanDataForMultipleResidences(residenceIDs, 30, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -127,8 +129,9 @@ func (s *TaskService) ListTasks(userID uint) (*responses.KanbanBoardResponse, er
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetTasksByResidence gets tasks for a specific residence (kanban board)
|
||||
func (s *TaskService) GetTasksByResidence(residenceID, userID uint, daysThreshold int) (*responses.KanbanBoardResponse, error) {
|
||||
// GetTasksByResidence gets tasks for a specific residence (kanban board).
|
||||
// The `now` parameter should be the start of day in the user's timezone for accurate overdue detection.
|
||||
func (s *TaskService) GetTasksByResidence(residenceID, userID uint, daysThreshold int, now time.Time) (*responses.KanbanBoardResponse, error) {
|
||||
// Check access
|
||||
hasAccess, err := s.residenceRepo.HasAccess(residenceID, userID)
|
||||
if err != nil {
|
||||
@@ -142,7 +145,8 @@ func (s *TaskService) GetTasksByResidence(residenceID, userID uint, daysThreshol
|
||||
daysThreshold = 30 // Default
|
||||
}
|
||||
|
||||
board, err := s.taskRepo.GetKanbanData(residenceID, daysThreshold)
|
||||
// Get kanban data using user's timezone-aware time
|
||||
board, err := s.taskRepo.GetKanbanData(residenceID, daysThreshold, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user