Files
HouseHoldKeaper/lib/features/home/data/calendar_dao.dart
Jean-Luc Makiola f718ee8483 feat(05-02): build CalendarStrip, CalendarTaskRow, CalendarDayList widgets
- Add totalTaskCount field to CalendarDayState to distinguish first-run from celebration
- Add getTaskCount() to CalendarDao (SELECT COUNT from tasks)
- CalendarStrip: 181-day horizontal scroll with German abbreviations, today highlighting, month boundary labels, scroll-to-today controller
- CalendarTaskRow: task name + room tag chip + checkbox, no relative date, isOverdue coral styling
- CalendarDayList: loading/error/first-run-empty/empty-day/celebration/has-tasks states, overdue section (today only), slide-out completion animation
- Update home_screen_test.dart and app_shell_test.dart to test new calendar providers instead of dailyPlanProvider
2026-03-16 21:35:35 +01:00

88 lines
2.8 KiB
Dart

import 'package:drift/drift.dart';
import '../../../core/database/database.dart';
import '../domain/daily_plan_models.dart';
part 'calendar_dao.g.dart';
/// DAO for calendar-based task queries.
///
/// Provides date-parameterized queries to answer:
/// - "What tasks are due on date X?"
/// - "What tasks are overdue relative to today?"
@DriftAccessor(tables: [Tasks, Rooms, TaskCompletions])
class CalendarDao extends DatabaseAccessor<AppDatabase>
with _$CalendarDaoMixin {
CalendarDao(super.attachedDatabase);
/// Watch tasks whose [nextDueDate] falls on the given calendar day.
///
/// Returns tasks sorted alphabetically by name.
/// Does NOT include overdue carry-over — only tasks originally due on [date].
Stream<List<TaskWithRoom>> watchTasksForDate(DateTime date) {
final startOfDay = DateTime(date.year, date.month, date.day);
final endOfDay = startOfDay.add(const Duration(days: 1));
final query = select(tasks).join([
innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
]);
query.where(
tasks.nextDueDate.isBiggerOrEqualValue(startOfDay) &
tasks.nextDueDate.isSmallerThanValue(endOfDay),
);
query.orderBy([OrderingTerm.asc(tasks.name)]);
return query.watch().map((rows) {
return rows.map((row) {
final task = row.readTable(tasks);
final room = row.readTable(rooms);
return TaskWithRoom(
task: task,
roomName: room.name,
roomId: room.id,
);
}).toList();
});
}
/// Returns the total count of tasks across all rooms and dates.
///
/// Used by the UI to distinguish first-run empty state from celebration state.
Future<int> getTaskCount() async {
final countExp = tasks.id.count();
final query = selectOnly(tasks)..addColumns([countExp]);
final result = await query.getSingle();
return result.read(countExp) ?? 0;
}
/// Watch tasks whose [nextDueDate] is strictly before [referenceDate].
///
/// Returns tasks sorted by [nextDueDate] ascending (oldest first).
/// Does NOT include tasks due on [referenceDate] itself.
Stream<List<TaskWithRoom>> watchOverdueTasks(DateTime referenceDate) {
final startOfReferenceDay = DateTime(
referenceDate.year,
referenceDate.month,
referenceDate.day,
);
final query = select(tasks).join([
innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
]);
query.where(tasks.nextDueDate.isSmallerThanValue(startOfReferenceDay));
query.orderBy([OrderingTerm.asc(tasks.nextDueDate)]);
return query.watch().map((rows) {
return rows.map((row) {
final task = row.readTable(tasks);
final room = row.readTable(rooms);
return TaskWithRoom(
task: task,
roomName: room.name,
roomId: room.id,
);
}).toList();
});
}
}