236 lines
6.4 KiB
Dart
236 lines
6.4 KiB
Dart
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<List<Task>>((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 <Task>[]);
|
|
}
|
|
|
|
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() ??
|
|
<String>[];
|
|
final officeIds =
|
|
assignmentsAsync.valueOrNull
|
|
?.where((assignment) => assignment.userId == profile.id)
|
|
.map((assignment) => assignment.officeId)
|
|
.toSet()
|
|
.toList() ??
|
|
<String>[];
|
|
|
|
if (allowedTicketIds.isEmpty && officeIds.isEmpty) {
|
|
return Stream.value(const <Task>[]);
|
|
}
|
|
|
|
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<List<TaskAssignment>>((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<TaskAssignmentsController>((
|
|
ref,
|
|
) {
|
|
final client = ref.watch(supabaseClientProvider);
|
|
return TaskAssignmentsController(client);
|
|
});
|
|
|
|
final tasksControllerProvider = Provider<TasksController>((ref) {
|
|
final client = ref.watch(supabaseClientProvider);
|
|
return TasksController(client);
|
|
});
|
|
|
|
class TasksController {
|
|
TasksController(this._client);
|
|
|
|
final SupabaseClient _client;
|
|
|
|
Future<void> updateTaskStatus({
|
|
required String taskId,
|
|
required String status,
|
|
}) async {
|
|
await _client.from('tasks').update({'status': status}).eq('id', taskId);
|
|
}
|
|
|
|
Future<void> 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<void> _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<List<String>> _fetchRoleUserIds({
|
|
required List<String> roles,
|
|
required String? excludeUserId,
|
|
}) async {
|
|
try {
|
|
final data = await _client
|
|
.from('profiles')
|
|
.select('id, role')
|
|
.inFilter('role', roles);
|
|
final rows = data as List<dynamic>;
|
|
final ids = rows
|
|
.map((row) => row['id'] as String?)
|
|
.whereType<String>()
|
|
.where((id) => id.isNotEmpty && id != excludeUserId)
|
|
.toList();
|
|
return ids;
|
|
} catch (_) {
|
|
return [];
|
|
}
|
|
}
|
|
}
|
|
|
|
class TaskAssignmentsController {
|
|
TaskAssignmentsController(this._client);
|
|
|
|
final SupabaseClient _client;
|
|
|
|
Future<void> replaceAssignments({
|
|
required String taskId,
|
|
required String? ticketId,
|
|
required List<String> newUserIds,
|
|
required List<String> 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<void> _notifyAssigned({
|
|
required String taskId,
|
|
required String? ticketId,
|
|
required List<String> 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<void> removeAssignment({
|
|
required String taskId,
|
|
required String userId,
|
|
}) async {
|
|
await _client
|
|
.from('task_assignments')
|
|
.delete()
|
|
.eq('task_id', taskId)
|
|
.eq('user_id', userId);
|
|
}
|
|
}
|