package models import ( "time" "task-dashboard/internal/config" ) type AtomSource string const ( SourceTrello AtomSource = "trello" SourceTodoist AtomSource = "todoist" SourceMeal AtomSource = "plantoeat" SourceGTasks AtomSource = "gtasks" SourceClaudomator AtomSource = "claudomator" ) type AtomType string const ( TypeTask AtomType = "task" TypeMeal AtomType = "meal" ) // Atom represents a unified unit of work or information type Atom struct { ID string Title string Description string Source AtomSource Type AtomType // Metadata URL string DueDate *time.Time CreatedAt time.Time Priority int // Normalized: 1 (Low) to 4 (Urgent) // UI Helpers (to be populated by mappers) SourceIcon string // e.g., "trello-icon.svg" or emoji ColorClass string // e.g., "border-blue-500" IsOverdue bool // True if due date is before today IsFuture bool // True if due date is after today HasSetTime bool // True if due time is not midnight (has specific time) IsRecurring bool // True if this is a recurring task // Original Data (for write operations) Raw interface{} } // ComputeUIFields calculates IsOverdue, IsFuture, and HasSetTime based on DueDate func (a *Atom) ComputeUIFields() { if a.DueDate == nil { return } tz := config.GetDisplayTimezone() now := config.Now() today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, tz) // Check if overdue (due date is before today) dueInTZ := a.DueDate.In(tz) dueDay := time.Date(dueInTZ.Year(), dueInTZ.Month(), dueInTZ.Day(), 0, 0, 0, 0, tz) a.IsOverdue = dueDay.Before(today) // Check if future (due date is 7+ days out — collapse in tasks tab) sevenDaysOut := today.AddDate(0, 0, 7) a.IsFuture = !dueDay.Before(sevenDaysOut) // Check if has set time (not midnight) a.HasSetTime = dueInTZ.Hour() != 0 || dueInTZ.Minute() != 0 } // TaskToAtom converts a Todoist Task to an Atom func TaskToAtom(t Task) Atom { // Todoist priority: 1 (normal) to 4 (urgent) // Keep as-is since it already matches our 1-4 scale priority := t.Priority if priority < 1 { priority = 1 } if priority > 4 { priority = 4 } return Atom{ ID: t.ID, Title: t.Content, Description: t.Description, Source: SourceTodoist, Type: TypeTask, URL: t.URL, DueDate: t.DueDate, CreatedAt: t.CreatedAt, Priority: priority, SourceIcon: "🔴", // Red circle for Todoist ColorClass: "border-red-500", IsRecurring: t.IsRecurring, Raw: t, } } // CardToAtom converts a Trello Card to an Atom func CardToAtom(c Card) Atom { // Trello doesn't have explicit priority, default to medium (2) // Can be enhanced later with label-based priority detection priority := 2 return Atom{ ID: c.ID, Title: c.Name, Description: c.ListName, // Use list name as description Source: SourceTrello, Type: TypeTask, URL: c.URL, DueDate: c.DueDate, CreatedAt: time.Time{}, // Trello ID contains timestamp, can be parsed later Priority: priority, SourceIcon: "📋", // Clipboard emoji for boards ColorClass: "border-blue-500", Raw: c, } } // StoryToAtom converts a ClaudomatorStory to an Atom func StoryToAtom(s ClaudomatorStory) Atom { return Atom{ ID: s.ID, Title: s.Title, Description: s.Description + " [" + s.ProjectID + "]", Source: SourceClaudomator, Type: TypeTask, Priority: 3, SourceIcon: "🤖", ColorClass: "border-purple-500", CreatedAt: s.CreatedAt, Raw: s, } } // GoogleTaskToAtom converts a Google Task to an Atom func GoogleTaskToAtom(t GoogleTask) Atom { // Google Tasks don't have explicit priority, default to medium (2) priority := 2 return Atom{ ID: t.ID, Title: t.Title, Description: t.Notes, Source: SourceGTasks, Type: TypeTask, URL: t.URL, DueDate: t.DueDate, CreatedAt: t.UpdatedAt, Priority: priority, SourceIcon: "🔵", // Blue circle for Google Tasks ColorClass: "border-blue-400", Raw: t, } }