import '../../internal/application/CommitmentClass.dart'; /// The container a set of commitments belongs to — "Workout", "Thesis", /// "Get the flat sorted". A goal owns its tasks through /// [Commitment.parentId]; it never carries debt itself, because debt belongs /// to the specific thing you said you would do, not the ambition behind it. class Goal { String? id; String title; String description; String category; /// The default class inherited by tasks created inside this goal. CommitmentClass defaultClass; DateTime? startDate; /// Optional deadline for the goal as a whole. DateTime? targetDate; /// Colour accent, stored as a hex string so the goal reads consistently /// wherever it appears. String colourHex; bool archived; // ── Derived, supplied by the server ───────────────────────────────────── int totalTasks; int completedTasks; int overdueTasks; /// Debt accrued across every task under this goal. double debtContribution; Goal({ this.id, this.title = "", this.description = "", this.category = "", this.defaultClass = CommitmentClass.Standard, this.startDate, this.targetDate, this.colourHex = "", this.archived = false, this.totalTasks = 0, this.completedTasks = 0, this.overdueTasks = 0, this.debtContribution = 0, }); factory Goal.fromJson(Map json) { return Goal( id: json['id'], title: json['title'] ?? "", description: json['description'] ?? "", category: json['category'] ?? "", defaultClass: getCommitmentClass(json['defaultClass']), startDate: DateTime.tryParse(json['startDate'] ?? ""), targetDate: DateTime.tryParse(json['targetDate'] ?? ""), colourHex: json['colourHex'] ?? "", archived: json['archived'] ?? false, totalTasks: json['totalTasks'] ?? 0, completedTasks: json['completedTasks'] ?? 0, overdueTasks: json['overdueTasks'] ?? 0, debtContribution: (json['debtContribution'] ?? 0).toDouble(), ); } Map toJson() { final Map data = {}; data['id'] = id; data['title'] = title; data['description'] = description; data['category'] = category; data['defaultClass'] = defaultClass.name; data['startDate'] = startDate?.toIso8601String(); data['targetDate'] = targetDate?.toIso8601String(); data['colourHex'] = colourHex; data['archived'] = archived; return data; } /// 0..1 across the goal's tasks. double get progress { if (totalTasks == 0) { return 0; } return completedTasks / totalTasks; } /// A goal is in trouble when a meaningful share of its tasks are past their /// windows, not merely because one slipped. bool get slipping { if (totalTasks == 0) { return false; } return overdueTasks / totalTasks >= 0.34; } int get remainingTasks { final int remaining = totalTasks - completedTasks; return remaining > 0 ? remaining : 0; } }