import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../models/task.dart'; import '../models/task_assignment.dart'; import 'profile_provider.dart'; import 'supabase_provider.dart'; import 'tickets_provider.dart'; import 'user_offices_provider.dart'; final tasksProvider = StreamProvider>((ref) { final client = ref.watch(supabaseClientProvider); final profileAsync = ref.watch(currentProfileProvider); final ticketsAsync = ref.watch(ticketsProvider); 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('tasks') .stream(primaryKey: ['id']) .order('queue_order', ascending: true) .order('created_at') .map((rows) => rows.map(Task.fromMap).toList()); } final allowedTicketIds = ticketsAsync.valueOrNull?.map((ticket) => ticket.id).toList() ?? []; final officeIds = assignmentsAsync.valueOrNull ?.where((assignment) => assignment.userId == profile.id) .map((assignment) => assignment.officeId) .toSet() .toList() ?? []; if (allowedTicketIds.isEmpty && officeIds.isEmpty) { return Stream.value(const []); } return client .from('tasks') .stream(primaryKey: ['id']) .order('queue_order', ascending: true) .order('created_at') .map( (rows) => rows.map(Task.fromMap).where((task) { final matchesTicket = task.ticketId != null && allowedTicketIds.contains(task.ticketId); final matchesOffice = task.officeId != null && officeIds.contains(task.officeId); return matchesTicket || matchesOffice; }).toList(), ); }); final taskAssignmentsProvider = StreamProvider>((ref) { final client = ref.watch(supabaseClientProvider); return client .from('task_assignments') .stream(primaryKey: ['task_id', 'user_id']) .map((rows) => rows.map(TaskAssignment.fromMap).toList()); }); final taskAssignmentsControllerProvider = Provider(( ref, ) { final client = ref.watch(supabaseClientProvider); return TaskAssignmentsController(client); }); final tasksControllerProvider = Provider((ref) { final client = ref.watch(supabaseClientProvider); return TasksController(client); }); class TasksController { TasksController(this._client); final SupabaseClient _client; Future updateTaskStatus({ required String taskId, required String status, }) async { await _client.from('tasks').update({'status': status}).eq('id', taskId); } Future createTask({ required String title, required String description, required String officeId, }) async { final actorId = _client.auth.currentUser?.id; final data = await _client .from('tasks') .insert({ 'title': title, 'description': description, 'office_id': officeId, }) .select('id') .single(); final taskId = data['id'] as String?; if (taskId == null) return; unawaited(_notifyCreated(taskId: taskId, actorId: actorId)); } Future _notifyCreated({ required String taskId, 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, 'task_id': taskId, '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 []; } } } class TaskAssignmentsController { TaskAssignmentsController(this._client); final SupabaseClient _client; Future replaceAssignments({ required String taskId, required String? ticketId, required List newUserIds, required List currentUserIds, }) async { final nextIds = newUserIds.toSet(); final currentIds = currentUserIds.toSet(); final toAdd = nextIds.difference(currentIds).toList(); final toRemove = currentIds.difference(nextIds).toList(); if (toAdd.isNotEmpty) { final rows = toAdd .map((userId) => {'task_id': taskId, 'user_id': userId}) .toList(); await _client.from('task_assignments').insert(rows); await _notifyAssigned(taskId: taskId, ticketId: ticketId, userIds: toAdd); } if (toRemove.isNotEmpty) { await _client .from('task_assignments') .delete() .eq('task_id', taskId) .inFilter('user_id', toRemove); } } Future _notifyAssigned({ required String taskId, required String? ticketId, required List userIds, }) async { if (userIds.isEmpty) return; try { final actorId = _client.auth.currentUser?.id; final rows = userIds .map( (userId) => { 'user_id': userId, 'actor_id': actorId, 'task_id': taskId, 'ticket_id': ticketId, 'type': 'assignment', }, ) .toList(); await _client.from('notifications').insert(rows); } catch (_) { return; } } Future removeAssignment({ required String taskId, required String userId, }) async { await _client .from('task_assignments') .delete() .eq('task_id', taskId) .eq('user_id', userId); } }