import 'package:flutter/material.dart'; import '../../../../core/widgets/premium_text_field.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import '../../domain/project_task.dart'; import '../../providers/project_provider.dart'; import '../../providers/user_provider.dart'; import '../../../../core/domain/user.dart'; class AddTaskSheet extends ConsumerStatefulWidget { final int projectId; final ProjectTask? task; const AddTaskSheet({super.key, required this.projectId, this.task}); @override ConsumerState createState() => _AddTaskSheetState(); } class _AddTaskSheetState extends ConsumerState { final _formKey = GlobalKey(); String _title = ''; String _description = ''; String _assigneeName = ''; bool _isBillable = false; double _hourlyRate = 0; bool _isLoading = false; bool get _isEditing => widget.task != null; @override void initState() { super.initState(); if (widget.task != null) { _title = widget.task!.title; _description = widget.task!.description ?? ''; _assigneeName = widget.task!.assigneeName ?? ''; _isBillable = widget.task!.isBillable; _hourlyRate = widget.task!.hourlyRate; } } @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, left: 24, right: 24, top: 24, ), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), child: SafeArea( child: Form( key: _formKey, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( _isEditing ? 'Edit Task' : 'New Task', style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), IconButton( icon: const Icon(LucideIcons.x), onPressed: () => Navigator.pop(context), ), ], ), const SizedBox(height: 16), PremiumTextField( labelText: 'Task Title', initialValue: _isEditing ? _title : null, prefixIcon: const Icon(LucideIcons.checkSquare), validator: (val) => val == null || val.isEmpty ? 'Required' : null, onSaved: (val) => _title = val!, ), const SizedBox(height: 16), PremiumTextField( maxLines: 3, labelText: 'Description (Optional)', initialValue: _isEditing ? _description : null, prefixIcon: const Icon(LucideIcons.alignLeft), onSaved: (val) => _description = val ?? '', ), const SizedBox(height: 16), Autocomplete( initialValue: _isEditing && _assigneeName.isNotEmpty ? TextEditingValue(text: _assigneeName) : null, optionsBuilder: (TextEditingValue textEditingValue) async { if (textEditingValue.text.isEmpty) { return const Iterable.empty(); } try { return await ref.read( userSearchProvider(textEditingValue.text).future, ); } catch (e) { return const Iterable.empty(); } }, displayStringForOption: (User option) => '${option.name} (${option.email})', onSelected: (User selection) { _assigneeName = selection.name; }, optionsViewBuilder: (context, onSelected, options) { return Align( alignment: Alignment.topLeft, child: Material( elevation: 4, borderRadius: BorderRadius.circular(12), color: Colors.white, child: ConstrainedBox( constraints: const BoxConstraints( maxHeight: 200, maxWidth: 300, ), child: ListView.builder( padding: EdgeInsets.zero, shrinkWrap: true, itemCount: options.length, itemBuilder: (BuildContext context, int index) { final User option = options.elementAt(index); return ListTile( title: Text( option.name, style: const TextStyle( fontWeight: FontWeight.bold, ), ), subtitle: Text( option.email, style: const TextStyle(color: Colors.grey), ), onTap: () => onSelected(option), ); }, ), ), ), ); }, fieldViewBuilder: ( context, textEditingController, focusNode, onFieldSubmitted, ) { return PremiumTextField( controller: textEditingController, focusNode: focusNode, labelText: 'Assignee Name (Optional)', prefixIcon: const Icon(LucideIcons.user), onSaved: (val) { if (_assigneeName.isEmpty) _assigneeName = val ?? ''; }, ); }, ), const SizedBox(height: 16), SwitchListTile( title: const Text('Is Billable?'), subtitle: const Text('Task time will be invoiced'), value: _isBillable, onChanged: (val) { setState(() { _isBillable = val; }); }, ), if (_isBillable) ...[ const SizedBox(height: 16), PremiumTextField( keyboardType: const TextInputType.numberWithOptions( decimal: true, ), labelText: 'Hourly Rate', initialValue: _isEditing && _hourlyRate > 0 ? _hourlyRate.toString() : null, prefixIcon: const Icon(LucideIcons.indianRupee), validator: (val) => _isBillable && (val == null || val.isEmpty) ? 'Required for billable tasks' : null, onSaved: (val) => _hourlyRate = double.tryParse(val ?? '0') ?? 0, ), ], const SizedBox(height: 24), ElevatedButton( style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), onPressed: _isLoading ? null : _submit, child: _isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ), ) : Text( _isEditing ? 'Save Changes' : 'Create Task', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), const SizedBox(height: 24), ], ), ), ), ), ); } Future _submit() async { if (!_formKey.currentState!.validate()) return; _formKey.currentState!.save(); setState(() => _isLoading = true); try { final repo = ref.read(projectRepositoryProvider); if (_isEditing) { await repo.updateTask( taskId: widget.task!.id, title: _title, description: _description.isNotEmpty ? _description : null, assigneeName: _assigneeName.isNotEmpty ? _assigneeName : null, isBillable: _isBillable, hourlyRate: _isBillable ? _hourlyRate : null, ); } else { await repo.createTask( projectId: widget.projectId, title: _title, description: _description.isNotEmpty ? _description : null, assigneeName: _assigneeName.isNotEmpty ? _assigneeName : null, isBillable: _isBillable, hourlyRate: _isBillable ? _hourlyRate : null, ); } ref.invalidate(projectTasksProvider(widget.projectId)); if (mounted) Navigator.pop(context); } catch (e) { if (mounted) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Error: $e'))); } } finally { if (mounted) setState(() => _isLoading = false); } } }