Add landing page, redesign emails, and return updated task on completion

- Integrate landing page into Go app (served at root /)
- Add STATIC_DIR config for static file serving
- Redesign all email templates with modern dark theme styling
- Add app icon to email headers
- Return updated task with kanban_column in completion response
- Update task DTO to include kanban column for client-side state updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-02 21:33:17 -06:00
parent 76579e8bf8
commit 3419b66097
15 changed files with 2689 additions and 283 deletions

View File

@@ -73,6 +73,7 @@ type TaskCompletionResponse struct {
Rating *int `json:"rating"`
Images []TaskCompletionImageResponse `json:"images"`
CreatedAt time.Time `json:"created_at"`
Task *TaskResponse `json:"task,omitempty"` // Updated task after completion
}
// TaskResponse represents a task in the API response
@@ -99,10 +100,11 @@ type TaskResponse struct {
ContractorID *uint `json:"contractor_id"`
IsCancelled bool `json:"is_cancelled"`
IsArchived bool `json:"is_archived"`
ParentTaskID *uint `json:"parent_task_id"`
CompletionCount int `json:"completion_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ParentTaskID *uint `json:"parent_task_id"`
CompletionCount int `json:"completion_count"`
KanbanColumn string `json:"kanban_column,omitempty"` // Which kanban column this task belongs to
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Note: Pagination removed - list endpoints now return arrays directly
@@ -340,3 +342,55 @@ func NewTaskCompletionListResponse(completions []models.TaskCompletion) []TaskCo
}
return results
}
// NewTaskCompletionWithTaskResponse creates a TaskCompletionResponse with the updated task included
func NewTaskCompletionWithTaskResponse(c *models.TaskCompletion, task *models.Task, daysThreshold int) TaskCompletionResponse {
resp := NewTaskCompletionResponse(c)
if task != nil {
taskResp := NewTaskResponse(task)
taskResp.KanbanColumn = DetermineKanbanColumn(task, daysThreshold)
resp.Task = &taskResp
}
return resp
}
// DetermineKanbanColumn determines which kanban column a task belongs to
// Uses the same logic as task_repo.go GetKanbanData
func DetermineKanbanColumn(task *models.Task, daysThreshold int) string {
if daysThreshold <= 0 {
daysThreshold = 30 // Default
}
now := time.Now().UTC()
threshold := now.AddDate(0, 0, daysThreshold)
// Priority order (same as GetKanbanData):
// 1. Cancelled
if task.IsCancelled {
return "cancelled_tasks"
}
// 2. Completed (has completions)
if len(task.Completions) > 0 {
return "completed_tasks"
}
// 3. In Progress
if task.Status != nil && task.Status.Name == "In Progress" {
return "in_progress_tasks"
}
// 4. Due date based
if task.DueDate != nil {
if task.DueDate.Before(now) {
return "overdue_tasks"
} else if task.DueDate.Before(threshold) {
return "due_soon_tasks"
}
}
// Default: upcoming
return "upcoming_tasks"
}