forked from eva/focus-flow
156 lines
4.1 KiB
Dart
156 lines
4.1 KiB
Dart
import 'models.dart';
|
|
|
|
/// Derived backlog filters for a unified backlog list.
|
|
enum BacklogFilter {
|
|
inbox,
|
|
pushed,
|
|
criticalMissed,
|
|
wishlist,
|
|
stale,
|
|
noRewardSet,
|
|
}
|
|
|
|
/// Sort options for a unified backlog list.
|
|
enum BacklogSortKey {
|
|
priority,
|
|
rewardVsEffort,
|
|
age,
|
|
project,
|
|
timesPushed,
|
|
}
|
|
|
|
/// Visual age bucket for backlog display.
|
|
enum BacklogStalenessMarker {
|
|
green,
|
|
blue,
|
|
purple,
|
|
}
|
|
|
|
/// Configurable thresholds for backlog age markers.
|
|
class BacklogStalenessSettings {
|
|
const BacklogStalenessSettings({
|
|
this.greenMaxAge = const Duration(days: 7),
|
|
this.blueMaxAge = const Duration(days: 30),
|
|
});
|
|
|
|
final Duration greenMaxAge;
|
|
final Duration blueMaxAge;
|
|
|
|
BacklogStalenessMarker markerFor({
|
|
required Task task,
|
|
required DateTime now,
|
|
}) {
|
|
final age = now.difference(task.createdAt);
|
|
|
|
if (age <= greenMaxAge) {
|
|
return BacklogStalenessMarker.green;
|
|
}
|
|
|
|
if (age <= blueMaxAge) {
|
|
return BacklogStalenessMarker.blue;
|
|
}
|
|
|
|
return BacklogStalenessMarker.purple;
|
|
}
|
|
}
|
|
|
|
/// Read-only backlog projection over the unified task list.
|
|
class BacklogView {
|
|
const BacklogView({
|
|
required this.tasks,
|
|
required this.now,
|
|
this.staleAfter = const Duration(days: 31),
|
|
this.stalenessSettings = const BacklogStalenessSettings(),
|
|
});
|
|
|
|
final List<Task> tasks;
|
|
final DateTime now;
|
|
final Duration staleAfter;
|
|
final BacklogStalenessSettings stalenessSettings;
|
|
|
|
List<Task> get backlogTasks {
|
|
return tasks.where((task) => task.isBacklog).toList(growable: false);
|
|
}
|
|
|
|
List<Task> filter(BacklogFilter filter) {
|
|
return backlogTasks.where((task) => _matchesFilter(task, filter)).toList(
|
|
growable: false,
|
|
);
|
|
}
|
|
|
|
List<Task> sorted(BacklogSortKey sortKey) {
|
|
final sortedTasks = [...backlogTasks];
|
|
sortedTasks.sort((a, b) => _compareTasks(a, b, sortKey));
|
|
|
|
return List<Task>.unmodifiable(sortedTasks);
|
|
}
|
|
|
|
BacklogStalenessMarker stalenessMarkerFor(Task task) {
|
|
return stalenessSettings.markerFor(task: task, now: now);
|
|
}
|
|
|
|
bool _matchesFilter(Task task, BacklogFilter filter) {
|
|
return switch (filter) {
|
|
BacklogFilter.inbox => task.projectId == 'inbox',
|
|
BacklogFilter.pushed =>
|
|
task.stats.manuallyPushedCount > 0 || task.stats.autoPushedCount > 0,
|
|
BacklogFilter.criticalMissed =>
|
|
task.type == TaskType.critical && task.stats.missedCount > 0,
|
|
BacklogFilter.wishlist => task.backlogTags.contains(BacklogTag.wishlist),
|
|
BacklogFilter.stale => now.difference(task.updatedAt) >= staleAfter,
|
|
BacklogFilter.noRewardSet => task.reward == RewardLevel.notSet,
|
|
};
|
|
}
|
|
|
|
int _compareTasks(Task a, Task b, BacklogSortKey sortKey) {
|
|
return switch (sortKey) {
|
|
BacklogSortKey.priority =>
|
|
_priorityRank(b.priority).compareTo(_priorityRank(a.priority)),
|
|
BacklogSortKey.rewardVsEffort =>
|
|
_rewardVsEffortScore(b).compareTo(_rewardVsEffortScore(a)),
|
|
BacklogSortKey.age => a.createdAt.compareTo(b.createdAt),
|
|
BacklogSortKey.project => a.projectId.compareTo(b.projectId),
|
|
BacklogSortKey.timesPushed => _timesPushed(b).compareTo(_timesPushed(a)),
|
|
};
|
|
}
|
|
}
|
|
|
|
int _priorityRank(PriorityLevel? priority) {
|
|
return switch (priority) {
|
|
PriorityLevel.veryLow => 0,
|
|
PriorityLevel.low => 1,
|
|
PriorityLevel.medium || null => 2,
|
|
PriorityLevel.high => 3,
|
|
PriorityLevel.veryHigh => 4,
|
|
};
|
|
}
|
|
|
|
int _rewardRank(RewardLevel reward) {
|
|
return switch (reward) {
|
|
RewardLevel.notSet => 0,
|
|
RewardLevel.veryLow => 1,
|
|
RewardLevel.low => 2,
|
|
RewardLevel.medium => 3,
|
|
RewardLevel.high => 4,
|
|
RewardLevel.veryHigh => 5,
|
|
};
|
|
}
|
|
|
|
int _difficultyRank(DifficultyLevel difficulty) {
|
|
return switch (difficulty) {
|
|
DifficultyLevel.notSet => 0,
|
|
DifficultyLevel.veryEasy => 1,
|
|
DifficultyLevel.easy => 2,
|
|
DifficultyLevel.medium => 3,
|
|
DifficultyLevel.hard => 4,
|
|
DifficultyLevel.veryHard => 5,
|
|
};
|
|
}
|
|
|
|
int _rewardVsEffortScore(Task task) {
|
|
return _rewardRank(task.reward) - _difficultyRank(task.difficulty);
|
|
}
|
|
|
|
int _timesPushed(Task task) {
|
|
return task.stats.manuallyPushedCount + task.stats.autoPushedCount;
|
|
}
|