import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_quill/flutter_quill.dart' as quill; import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart'; import 'package:flutter_html/flutter_html.dart' as html; 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/Directory/comment_editor_card.dart'; class NotesView extends StatelessWidget { final NotesController controller = Get.find(); final TextEditingController searchController = TextEditingController(); NotesView({super.key}); Future _refreshNotes() async { try { await controller.fetchNotes(); } catch (e, st) { debugPrint('Error refreshing notes: $e'); debugPrintStack(stackTrace: st); } } String _convertDeltaToHtml(dynamic delta) { final buffer = StringBuffer(); bool inList = false; for (var op in delta.toList()) { final data = op.data?.toString() ?? ''; final attr = op.attributes ?? {}; final isListItem = attr.containsKey('list'); if (isListItem && !inList) { buffer.write(''); inList = false; } if (isListItem) buffer.write('
  • '); if (attr.containsKey('bold')) buffer.write(''); if (attr.containsKey('italic')) buffer.write(''); if (attr.containsKey('underline')) buffer.write(''); if (attr.containsKey('strike')) buffer.write(''); if (attr.containsKey('link')) buffer.write(''); buffer.write(data.replaceAll('\n', '')); if (attr.containsKey('link')) buffer.write(''); if (attr.containsKey('strike')) buffer.write(''); if (attr.containsKey('underline')) buffer.write(''); if (attr.containsKey('italic')) buffer.write(''); if (attr.containsKey('bold')) buffer.write(''); if (isListItem) buffer.write('
  • '); else if (data.contains('\n')) buffer.write('
    '); } if (inList) buffer.write(''); return buffer.toString(); } @override Widget build(BuildContext context) { return Column( children: [ /// 🔍 Search + Refresh (Top Row) 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(10), borderSide: BorderSide(color: Colors.grey.shade300), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.grey.shade300), ), ), ), ), ), MySpacing.width(8), Tooltip( message: 'Refresh Notes', child: InkWell( borderRadius: BorderRadius.circular(24), onTap: _refreshNotes, child: MouseRegion( cursor: SystemMouseCursors.click, child: const Padding( padding: EdgeInsets.all(4), child: Icon(Icons.refresh, color: Colors.green, size: 26), ), ), ), ), ], ), ), /// 📄 Notes List View Expanded( child: Obx(() { if (controller.isLoading.value) { return const Center(child: CircularProgressIndicator()); } final notes = controller.filteredNotesList; if (notes.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.note_alt_outlined, size: 60, color: Colors.grey), const SizedBox(height: 12), MyText.bodyMedium('No notes found.', fontWeight: 500), ], ), ); } return ListView.separated( padding: MySpacing.only(left: 8, right: 8, top: 4, bottom: 80), itemCount: notes.length, separatorBuilder: (_, __) => MySpacing.height(12), itemBuilder: (_, index) { final note = notes[index]; final isEditing = controller.editingNoteId.value == note.id; final initials = note.contactName.trim().isNotEmpty ? note.contactName.trim().split(' ').map((e) => e[0]).take(2).join().toUpperCase() : "NA"; final createdDate = DateTimeUtils.convertUtcToLocal(note.createdAt.toString(), format: 'dd MMM yyyy'); final createdTime = DateTimeUtils.convertUtcToLocal(note.createdAt.toString(), format: 'hh:mm a'); final decodedDelta = HtmlToDelta().convert(note.note); final quillController = isEditing ? quill.QuillController( document: quill.Document.fromDelta(decodedDelta), selection: TextSelection.collapsed(offset: decodedDelta.length), ) : null; return AnimatedContainer( duration: const Duration(milliseconds: 250), padding: MySpacing.xy(12, 12), decoration: BoxDecoration( color: isEditing ? Colors.indigo[50] : Colors.white, border: Border.all( color: isEditing ? Colors.indigo : Colors.grey.shade300, width: 1.1, ), borderRadius: BorderRadius.circular(12), boxShadow: const [ BoxShadow(color: Colors.black12, blurRadius: 4, offset: Offset(0, 2)), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// Header Row Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Avatar(firstName: initials, lastName: '', size: 40), MySpacing.width(12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.titleSmall( "${note.contactName} (${note.organizationName})", fontWeight: 600, overflow: TextOverflow.ellipsis, color: Colors.indigo[800], ), MyText.bodySmall( "by ${note.createdBy.firstName} • $createdDate, $createdTime", color: Colors.grey[600], ), ], ), ), IconButton( icon: Icon( isEditing ? Icons.close : Icons.edit, color: Colors.indigo, size: 20, ), onPressed: () { controller.editingNoteId.value = isEditing ? null : note.id; }, ), ], ), MySpacing.height(12), /// Content if (isEditing && quillController != null) CommentEditorCard( controller: quillController, onCancel: () => controller.editingNoteId.value = null, onSave: (quillCtrl) async { final delta = quillCtrl.document.toDelta(); final htmlOutput = _convertDeltaToHtml(delta); final updated = note.copyWith(note: htmlOutput); await controller.updateNote(updated); controller.editingNoteId.value = null; }, ) else html.Html( data: note.note, style: { "body": html.Style( margin: html.Margins.zero, padding: html.HtmlPaddings.zero, fontSize: html.FontSize.medium, color: Colors.black87, ), }, ), ], ), ); }, ); }), ), ], ); } }