import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:marco/helpers/widgets/my_refresh_indicator.dart'; import 'package:marco/controller/directory/notes_controller.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/avatar.dart'; import 'package:marco/helpers/utils/date_time_utils.dart'; import 'package:marco/helpers/widgets/my_confirmation_dialog.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; class NotesView extends StatefulWidget { const NotesView({super.key}); @override State createState() => _NotesViewState(); } class _NotesViewState extends State with UIMixin { final NotesController controller = Get.find(); final TextEditingController searchController = TextEditingController(); Future _refreshNotes() async { try { await controller.fetchNotes(); } catch (e, st) { debugPrint('Error refreshing notes: $e'); debugPrintStack(stackTrace: st); } } Widget _buildEmptyState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.perm_contact_cal, size: 60, color: Colors.grey), MySpacing.height(18), MyText.titleMedium( 'No matching notes found.', fontWeight: 600, color: Colors.grey, ), MySpacing.height(10), MyText.bodySmall( 'Try adjusting your filters or refresh to reload.', color: Colors.grey, ), ], ), ); } Widget _buildNoteItem(note) { final isEditing = controller.editingNoteId.value == note.id; final textController = TextEditingController(text: note.note); final initials = note.contactName.trim().isNotEmpty ? note.contactName .trim() .split(' ') .map((e) => e[0]) .take(2) .join() .toUpperCase() : "NA"; return Container( margin: const EdgeInsets.symmetric(vertical: 6), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5), border: Border.all(color: Colors.grey.shade200), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header: Avatar + Name + Timestamp + Actions Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Avatar(firstName: initials, lastName: '', size: 40), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.titleSmall( "${note.contactName} (${note.organizationName})", fontWeight: 600, color: Colors.black87, ), const SizedBox(height: 2), MyText.bodySmall( "by ${note.createdBy.firstName} ${note.createdBy.lastName} • " "${DateTimeUtils.convertUtcToLocal(note.createdAt.toString(), format: 'dd MMM yyyy, hh:mm a')}", color: Colors.grey[600], ), ], ), ), Row( mainAxisSize: MainAxisSize.min, children: [ if (!note.isActive) IconButton( icon: const Icon(Icons.restore, size: 18, color: Colors.green), tooltip: "Restore", splashRadius: 18, onPressed: () async { await Get.dialog( ConfirmDialog( title: "Restore Note", message: "Are you sure you want to restore this note?", confirmText: "Restore", confirmColor: Colors.green, icon: Icons.restore, onConfirm: () async { await controller.restoreOrDeleteNote(note, restore: true); }, ), ); }, ), if (note.isActive) ...[ IconButton( icon: Icon(isEditing ? Icons.close : Icons.edit_outlined, color: Colors.indigo, size: 18), splashRadius: 18, onPressed: () { controller.editingNoteId.value = isEditing ? null : note.id; }, ), IconButton( icon: const Icon(Icons.delete_outline, size: 18, color: Colors.red), splashRadius: 18, onPressed: () async { await Get.dialog( ConfirmDialog( title: "Delete Note", message: "Are you sure you want to delete this note?", confirmText: "Delete", confirmColor: Colors.red, icon: Icons.delete_forever, onConfirm: () async { await controller.restoreOrDeleteNote(note, restore: false); }, ), ); }, ), ], ], ), ], ), const SizedBox(height: 8), // Content: TextField when editing or plain text if (isEditing) Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextField( controller: textController, maxLines: null, minLines: 5, decoration: InputDecoration( hintText: "Edit note...", border: OutlineInputBorder( borderRadius: BorderRadius.circular(5)), contentPadding: const EdgeInsets.all(12), ), ), const SizedBox(height: 12), Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: () => controller.editingNoteId.value = null, icon: const Icon(Icons.close, color: Colors.white), label: MyText.bodyMedium( "Cancel", color: Colors.white, fontWeight: 600, ), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.symmetric(vertical: 8), ), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton.icon( onPressed: () async { final updated = note.copyWith(note: textController.text); await controller.updateNote(updated); controller.editingNoteId.value = null; }, icon: const Icon(Icons.check_circle_outline, color: Colors.white), label: MyText.bodyMedium( "Save", color: Colors.white, fontWeight: 600, ), style: ElevatedButton.styleFrom( backgroundColor: contentTheme.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.symmetric(vertical: 8), ), ), ), ], ), ], ) else Text( note.note, style: TextStyle(color: Colors.grey[800], fontSize: 14), ), ], ), ); } @override Widget build(BuildContext context) { return Column( children: [ // Search Padding( padding: MySpacing.xy(8, 8), child: Row( children: [ Expanded( child: SizedBox( height: 35, child: TextField( controller: searchController, onChanged: (value) => controller.searchQuery.value = value, decoration: InputDecoration( contentPadding: const EdgeInsets.symmetric(horizontal: 6), prefixIcon: const Icon(Icons.search, size: 20, color: Colors.grey), hintText: 'Search notes...', filled: true, fillColor: Colors.white, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: BorderSide(color: Colors.grey.shade300), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: BorderSide(color: Colors.grey.shade300), ), ), ), ), ), ], ), ), // Notes List Expanded( child: Obx(() { if (controller.isLoading.value) { return const Center(child: CircularProgressIndicator()); } final notes = controller.filteredNotesList; if (notes.isEmpty) { return MyRefreshIndicator( onRefresh: _refreshNotes, child: LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: ConstrainedBox( constraints: BoxConstraints(minHeight: constraints.maxHeight), child: Center( child: _buildEmptyState(), ), ), ); }, ), ); } return MyRefreshIndicator( onRefresh: _refreshNotes, child: ListView.separated( physics: const AlwaysScrollableScrollPhysics(), padding: MySpacing.only(left: 8, right: 8, top: 4, bottom: 80), itemCount: notes.length, separatorBuilder: (_, __) => MySpacing.height(12), itemBuilder: (_, index) => Obx(() => _buildNoteItem(notes[index])), ), ); }), ), ], ); } }