timetracker/lib/features/entries/presentation/entries_screen.dart
2026-08-03 21:51:48 +02:00

135 lines
4.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:timetrack/features/entries/domain/entries_provider.dart';
import 'package:timetrack/features/entries/domain/time_entry.dart';
import 'package:timetrack/features/entries/presentation/widgets/date_range_filter.dart';
import 'package:timetrack/features/entries/presentation/widgets/day_header.dart';
import 'package:timetrack/features/entries/presentation/widgets/entry_form_sheet.dart';
import 'package:timetrack/features/entries/presentation/widgets/entry_list_tile.dart';
import 'package:timetrack/features/projects/domain/project.dart';
import 'package:timetrack/features/projects/domain/projects_provider.dart';
class EntriesScreen extends ConsumerStatefulWidget {
const EntriesScreen({super.key});
@override
ConsumerState<EntriesScreen> createState() => _EntriesScreenState();
}
class _EntriesScreenState extends ConsumerState<EntriesScreen> {
DateRangeFilter _filter = DateRangeFilter.thisWeek;
@override
Widget build(BuildContext context) {
final (from, to) = dateRangeBounds(_filter);
final entriesAsync = ref.watch(
entriesByDateRangeProvider(rangeFrom: from, rangeTo: to),
);
final projectsAsync = ref.watch(allProjectsProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Entries'),
actions: [
IconButton(
icon: const Icon(Icons.add),
tooltip: 'Add entry',
onPressed: () => EntryFormSheet.show(context),
),
],
),
body: Column(
children: [
DateRangeFilterChips(
selected: _filter,
onChanged: (f) => setState(() => _filter = f),
),
Expanded(
child: entriesAsync.when(
data: (entries) => projectsAsync.when(
data: (projects) => _buildList(entries, projects),
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('Error: $e')),
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('Error: $e')),
),
),
],
),
);
}
Widget _buildList(List<TimeEntry> entries, List<Project> projects) {
if (entries.isEmpty) {
return const Center(
key: Key('entries_empty'),
child: Text('No entries yet.'),
);
}
final projectMap = {for (final p in projects) p.id: p};
// Group by date
final grouped = <DateTime, List<TimeEntry>>{};
for (final entry in entries) {
final day = DateTime(
entry.startTime.year,
entry.startTime.month,
entry.startTime.day,
);
grouped.putIfAbsent(day, () => []).add(entry);
}
final sortedDays = grouped.keys.toList()
..sort((a, b) => b.compareTo(a));
return ListView.builder(
itemCount: sortedDays.length,
itemBuilder: (context, index) {
final day = sortedDays[index];
final dayEntries = grouped[day]!;
final totalSeconds = dayEntries.fold<int>(
0,
(sum, e) => sum + (e.durationSeconds ?? 0),
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DayHeader(
date: day,
totalDuration: Duration(seconds: totalSeconds),
),
...dayEntries.map((entry) {
final project = projectMap[entry.projectId];
return EntryListTile(
key: Key('entry_tile_${entry.id}'),
entry: entry,
projectName: project?.name ?? '',
projectColor: project?.colorValue ?? 0xFF888888,
onTap: () => EntryFormSheet.show(context, existing: entry),
onDelete: () => _deleteWithUndo(entry),
);
}),
],
);
},
);
}
void _deleteWithUndo(TimeEntry entry) {
ref.read(entriesNotifierProvider.notifier).delete(entry.id);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Entry deleted'),
action: SnackBarAction(
label: 'Undo',
onPressed: () =>
ref.read(entriesNotifierProvider.notifier).create(entry),
),
),
);
}
}