import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../models/office.dart'; import '../models/ticket.dart'; import '../models/ticket_message.dart'; import 'profile_provider.dart'; import 'supabase_provider.dart'; import 'user_offices_provider.dart'; final officesProvider = StreamProvider>((ref) { final client = ref.watch(supabaseClientProvider); return client .from('offices') .stream(primaryKey: ['id']) .order('name') .map((rows) => rows.map(Office.fromMap).toList()); }); final officesOnceProvider = FutureProvider>((ref) async { final client = ref.watch(supabaseClientProvider); final rows = await client.from('offices').select().order('name'); return (rows as List) .map((row) => Office.fromMap(row as Map)) .toList(); }); final officesControllerProvider = Provider((ref) { final client = ref.watch(supabaseClientProvider); return OfficesController(client); }); final ticketsProvider = StreamProvider>((ref) { final client = ref.watch(supabaseClientProvider); final profileAsync = ref.watch(currentProfileProvider); final assignmentsAsync = ref.watch(userOfficesProvider); final profile = profileAsync.valueOrNull; if (profile == null) { return Stream.value(const []); } final isGlobal = profile.role == 'admin' || profile.role == 'dispatcher' || profile.role == 'it_staff'; if (isGlobal) { return client .from('tickets') .stream(primaryKey: ['id']) .order('created_at', ascending: false) .map((rows) => rows.map(Ticket.fromMap).toList()); } final officeIds = assignmentsAsync.valueOrNull ?.where((assignment) => assignment.userId == profile.id) .map((assignment) => assignment.officeId) .toSet() .toList() ?? []; if (officeIds.isEmpty) { return Stream.value(const []); } return client .from('tickets') .stream(primaryKey: ['id']) .inFilter('office_id', officeIds) .order('created_at', ascending: false) .map((rows) => rows.map(Ticket.fromMap).toList()); }); final ticketMessagesProvider = StreamProvider.family, String>((ref, ticketId) { final client = ref.watch(supabaseClientProvider); return client .from('ticket_messages') .stream(primaryKey: ['id']) .eq('ticket_id', ticketId) .order('created_at', ascending: false) .map((rows) => rows.map(TicketMessage.fromMap).toList()); }); final ticketMessagesAllProvider = StreamProvider>((ref) { final client = ref.watch(supabaseClientProvider); return client .from('ticket_messages') .stream(primaryKey: ['id']) .order('created_at', ascending: false) .map((rows) => rows.map(TicketMessage.fromMap).toList()); }); final taskMessagesProvider = StreamProvider.family, String>( (ref, taskId) { final client = ref.watch(supabaseClientProvider); return client .from('ticket_messages') .stream(primaryKey: ['id']) .eq('task_id', taskId) .order('created_at', ascending: false) .map((rows) => rows.map(TicketMessage.fromMap).toList()); }, ); final ticketsControllerProvider = Provider((ref) { final client = ref.watch(supabaseClientProvider); return TicketsController(client); }); class TicketsController { TicketsController(this._client); final SupabaseClient _client; Future createTicket({ required String subject, required String description, required String officeId, }) async { final actorId = _client.auth.currentUser?.id; final data = await _client .from('tickets') .insert({ 'subject': subject, 'description': description, 'office_id': officeId, 'creator_id': _client.auth.currentUser?.id, }) .select('id') .single(); final ticketId = data['id'] as String?; if (ticketId == null) return; unawaited(_notifyCreated(ticketId: ticketId, actorId: actorId)); } Future _notifyCreated({ required String ticketId, required String? actorId, }) async { try { final recipients = await _fetchRoleUserIds( roles: const ['dispatcher', 'it_staff'], excludeUserId: actorId, ); if (recipients.isEmpty) return; final rows = recipients .map( (userId) => { 'user_id': userId, 'actor_id': actorId, 'ticket_id': ticketId, 'type': 'created', }, ) .toList(); await _client.from('notifications').insert(rows); } catch (_) { return; } } Future> _fetchRoleUserIds({ required List roles, required String? excludeUserId, }) async { try { final data = await _client .from('profiles') .select('id, role') .inFilter('role', roles); final rows = data as List; final ids = rows .map((row) => row['id'] as String?) .whereType() .where((id) => id.isNotEmpty && id != excludeUserId) .toList(); return ids; } catch (_) { return []; } } Future sendTicketMessage({ required String ticketId, required String content, }) async { final data = await _client .from('ticket_messages') .insert({ 'ticket_id': ticketId, 'content': content, 'sender_id': _client.auth.currentUser?.id, }) .select() .single(); return TicketMessage.fromMap(data); } Future sendTaskMessage({ required String taskId, required String? ticketId, required String content, }) async { final payload = { 'task_id': taskId, 'content': content, 'sender_id': _client.auth.currentUser?.id, }; if (ticketId != null) { payload['ticket_id'] = ticketId; } final data = await _client .from('ticket_messages') .insert(payload) .select() .single(); return TicketMessage.fromMap(data); } Future updateTicketStatus({ required String ticketId, required String status, }) async { await _client.from('tickets').update({'status': status}).eq('id', ticketId); } } class OfficesController { OfficesController(this._client); final SupabaseClient _client; Future createOffice({required String name}) async { await _client.from('offices').insert({'name': name}); } Future updateOffice({required String id, required String name}) async { await _client.from('offices').update({'name': name}).eq('id', id); } Future deleteOffice({required String id}) async { await _client.from('offices').delete().eq('id', id); } }