import 'package:flutter/foundation.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/enums_and_consts/consts.dart'; import 'package:flux/features/notes/models/note_model.dart'; import 'package:get_it/get_it.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class NotesRepository { final _supabase = GetIt.I.get(); String get _companyId => GetIt.I.get().state.company!.id!; String get _currentStaffId => GetIt.I.get().state.currentStaffMember!.id!; /// Recupera tutte le note visibili dall'utente corrente: /// 1. Note create da lui /// 2. Note condivise a tutti (is_shared_all = true) /// 3. Note in cui l'utente è esplicitamente un collaboratore Future> getNotes() async { try { // Usiamo la sintassi avanzata di Supabase per fare il filtro OR sulle relazioni // Inoltre tiriamo giù note_collaborators e l'anagrafica dei membri dello staff associati final response = await _supabase .from(Tables.notes) .select(''' *, ${Tables.noteCollaborators}!inner ( staff_id, ${Tables.staffMembers} (*) ) ''') // Filtro multi-tenant di sicurezza (già ridondante con RLS ma ottimo per performance) .eq('company_id', _companyId) // Questa è la magia: l'utente vede la nota se è sua, se è pubblica o se è tra i collaboratori .or( 'created_by.eq.$_currentStaffId,is_shared_all.eq.true,note_collaborators.staff_id.eq.$_currentStaffId', ) // Ordiniamo prima per sticky (pinned) e poi per data di aggiornamento .order('is_pinned', ascending: false) .order('updated_at', ascending: false); return (response as List) .map((json) => NoteModel.fromMap(json as Map)) .toList(); } catch (e) { // In caso di errore sulla join !inner se non ci sono collaboratori, // facciamo un fallback pulito su una query standard e uniamo i dati. return _getNotesFallback(); } } /// Fallback sicuro nel caso la query complessa con !inner si inceppi se la lista collaboratori è vuota Future> _getNotesFallback() async { final response = await _supabase .from(Tables.notes) .select(''' *, ${Tables.noteCollaborators} ( staff_id, ${Tables.staffMembers} (*) ) ''') .eq('company_id', _companyId) .order('is_pinned', ascending: false) .order('updated_at', ascending: false); final allNotes = (response as List) .map((json) => NoteModel.fromMap(json as Map)) .toList(); // Filtriamo lato codice per essere sicuri della visibilità return allNotes.where((note) { return note.createdBy == _currentStaffId || note.isSharedAll || note.collaboratorIds.contains(_currentStaffId); }).toList(); } Stream> notesStream({ required String companyId, required String currentStaffId, }) { return _supabase .from(Tables.notes) .stream(primaryKey: ['id']) .eq('company_id', companyId) .order('is_pinned', ascending: false) // Nota: puoi ordinare solo per un campo alla volta nello stream nativo di Supabase. // Ordiniamo per is_pinned, l'ordinamento per data lo facciamo al volo in RAM se serve, // oppure ordiniamo per updated_at e il pin lo gestiamo via software. .map((rawNotes) { return rawNotes.map((json) => NoteModel.fromMap(json)).where((note) { // Filtro multi-tenant di sicurezza in memoria return note.createdBy == currentStaffId || note.isSharedAll || note.collaboratorIds.contains(currentStaffId); }).toList(); }); } Future saveNote(NoteModel note) async { // 1. Eseguiamo l'upsert sulla tabella principale 'notes' // Supabase gestisce in automatico: se l'id è null inserisce, se c'è fa update. // Usiamo il .select().single() per farci restituire l'id generato (in caso di nuova nota) final noteResponse = await _supabase .from(Tables.notes) .upsert({ if (note.id != null) 'id': note.id, 'company_id': note.companyId, 'created_by': note.createdBy, 'title': note.title, 'content': note.content, 'color': note.color, 'is_pinned': note.isPinned, 'is_shared_all': note.isSharedAll, 'updated_at': DateTime.now().toIso8601String(), }) .select('id') .single(); final noteId = noteResponse['id'] as String; // Se la nota è condivisa con tutti, spazziamo via eventuali collaboratori singoli e usciamo if (note.isSharedAll) { await _supabase.from('note_collaborators').delete().eq('note_id', noteId); return noteId; } // 2. LA STRATEGIA DEL PIAZZA PULITA SUI COLLABORATORI // Prima di tutto eliminiamo TUTTI i collaboratori attuali per questa specifica nota await _supabase.from('note_collaborators').delete().eq('note_id', noteId); // 3. RE-INSERIMENTO DELLA LISTA AGGIORNATA if (note.collaboratorIds.isNotEmpty) { final collaboratorsToInsert = note.collaboratorIds .map( (staffId) => { 'note_id': noteId, 'staff_id': staffId, 'company_id': note.companyId, // Aggiunto questo! }, ) .toList(); // Consiglio da pro: avvolgi l'insert in un try-catch per stampare l'errore esatto a console try { await _supabase .from(Tables.noteCollaborators) .insert(collaboratorsToInsert); } catch (e) { debugPrint('Errore inserimento collaboratori: $e'); throw Exception('Impossibile aggiungere i collaboratori alla nota.'); } } // Restituiamo l'id alla UI (fondamentale per la nostra logica Ninja di creazione) return noteId; } /// Elimina una nota (i collaboratori si cancellano in cascata grazie al vincolo del DB) Future deleteNote(String noteId) async { await _supabase.from(Tables.notes).delete().eq('id', noteId); } }