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/projects/domain/project.dart'; import 'package:timetrack/features/projects/domain/projects_provider.dart'; class EntryFormSheet extends ConsumerStatefulWidget { const EntryFormSheet({super.key, this.existing}); final TimeEntry? existing; static Future show(BuildContext context, {TimeEntry? existing}) { return showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (_) => EntryFormSheet(existing: existing), ); } @override ConsumerState createState() => _EntryFormSheetState(); } class _EntryFormSheetState extends ConsumerState { Project? _project; late DateTime _startTime; late DateTime _endTime; final _noteController = TextEditingController(); String? _error; @override void initState() { super.initState(); final now = DateTime.now(); _startTime = widget.existing?.startTime ?? now; _endTime = widget.existing?.endTime ?? now; _noteController.text = widget.existing?.note ?? ''; } @override void dispose() { _noteController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final projectsAsync = ref.watch(activeProjectsProvider); return Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom + 16, top: 16, left: 16, right: 16, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.existing == null ? 'Add Entry' : 'Edit Entry', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 16), // Project picker projectsAsync.when( data: (projects) => DropdownButtonFormField( initialValue: _project ?? (widget.existing != null ? projects .where((p) => p.id == widget.existing!.projectId) .firstOrNull : null), items: projects .map((p) => DropdownMenuItem( value: p, child: Row( children: [ CircleAvatar( backgroundColor: Color(p.colorValue), radius: 8, ), const SizedBox(width: 8), Text(p.name), ], ), )) .toList(), onChanged: (p) => setState(() => _project = p), decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Project', ), ), loading: () => const LinearProgressIndicator(), error: (e, _) => Text('Error: $e'), ), const SizedBox(height: 12), // Start / End time Row( children: [ Expanded( child: _TimeField( label: 'Start', value: _startTime, onChanged: (dt) => setState(() => _startTime = dt), ), ), const SizedBox(width: 12), Expanded( child: _TimeField( label: 'End', value: _endTime, onChanged: (dt) => setState(() => _endTime = dt), ), ), ], ), const SizedBox(height: 12), // Note TextField( controller: _noteController, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Note (optional)', ), ), if (_error != null) ...[ const SizedBox(height: 8), Text(_error!, style: TextStyle(color: Theme.of(context).colorScheme.error)), ], const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), const SizedBox(width: 8), FilledButton( onPressed: _save, child: const Text('Save'), ), ], ), ], ), ); } Future _save() async { if (_project == null) { setState(() => _error = 'Please select a project.'); return; } if (!_endTime.isAfter(_startTime)) { setState(() => _error = 'End time must be after start time.'); return; } final duration = _endTime.difference(_startTime); final notifier = ref.read(entriesNotifierProvider.notifier); final now = DateTime.now(); if (widget.existing == null) { await notifier.create(TimeEntry( id: 0, projectId: _project!.id, startTime: _startTime, endTime: _endTime, durationSeconds: duration.inSeconds, note: _noteController.text.trim().isEmpty ? null : _noteController.text.trim(), createdAt: now, )); } else { await notifier.updateEntry(widget.existing!.copyWith( projectId: _project!.id, startTime: _startTime, endTime: _endTime, durationSeconds: duration.inSeconds, note: _noteController.text.trim().isEmpty ? null : _noteController.text.trim(), )); } if (mounted) Navigator.pop(context); } } class _TimeField extends StatelessWidget { const _TimeField({ required this.label, required this.value, required this.onChanged, }); final String label; final DateTime value; final ValueChanged onChanged; @override Widget build(BuildContext context) { return InkWell( onTap: () async { final date = await showDatePicker( context: context, initialDate: value, firstDate: DateTime(2020), lastDate: DateTime.now().add(const Duration(days: 1)), ); if (date == null || !context.mounted) return; final time = await showTimePicker( context: context, initialTime: TimeOfDay.fromDateTime(value), ); if (time == null) return; onChanged(DateTime(date.year, date.month, date.day, time.hour, time.minute)); }, child: InputDecorator( decoration: InputDecoration( border: const OutlineInputBorder(), labelText: label, ), child: Text( '${value.day.toString().padLeft(2, '0')}.${value.month.toString().padLeft(2, '0')} ' '${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}', ), ), ); } }