Initial commit
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
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<List<Office>>((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<List<Office>>((ref) async {
|
||||
final client = ref.watch(supabaseClientProvider);
|
||||
final rows = await client.from('offices').select().order('name');
|
||||
return (rows as List<dynamic>)
|
||||
.map((row) => Office.fromMap(row as Map<String, dynamic>))
|
||||
.toList();
|
||||
});
|
||||
|
||||
final officesControllerProvider = Provider<OfficesController>((ref) {
|
||||
final client = ref.watch(supabaseClientProvider);
|
||||
return OfficesController(client);
|
||||
});
|
||||
|
||||
final ticketsProvider = StreamProvider<List<Ticket>>((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 <Ticket>[]);
|
||||
}
|
||||
|
||||
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() ??
|
||||
<String>[];
|
||||
if (officeIds.isEmpty) {
|
||||
return Stream.value(const <Ticket>[]);
|
||||
}
|
||||
|
||||
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<List<TicketMessage>, 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<List<TicketMessage>>((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<List<TicketMessage>, 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<TicketsController>((ref) {
|
||||
final client = ref.watch(supabaseClientProvider);
|
||||
return TicketsController(client);
|
||||
});
|
||||
|
||||
class TicketsController {
|
||||
TicketsController(this._client);
|
||||
|
||||
final SupabaseClient _client;
|
||||
|
||||
Future<void> 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<void> _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<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 [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<TicketMessage> 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<TicketMessage> sendTaskMessage({
|
||||
required String taskId,
|
||||
required String? ticketId,
|
||||
required String content,
|
||||
}) async {
|
||||
final payload = <String, dynamic>{
|
||||
'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<void> 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<void> createOffice({required String name}) async {
|
||||
await _client.from('offices').insert({'name': name});
|
||||
}
|
||||
|
||||
Future<void> updateOffice({required String id, required String name}) async {
|
||||
await _client.from('offices').update({'name': name}).eq('id', id);
|
||||
}
|
||||
|
||||
Future<void> deleteOffice({required String id}) async {
|
||||
await _client.from('offices').delete().eq('id', id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user