import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../models/app_settings.dart'; import '../models/duty_schedule.dart'; import '../models/swap_request.dart'; import 'profile_provider.dart'; import 'supabase_provider.dart'; final geofenceProvider = FutureProvider((ref) async { final client = ref.watch(supabaseClientProvider); final data = await client .from('app_settings') .select() .eq('key', 'geofence') .maybeSingle(); if (data == null) return null; final setting = AppSetting.fromMap(data); return GeofenceConfig.fromJson(setting.value); }); final dutySchedulesProvider = StreamProvider>((ref) { final client = ref.watch(supabaseClientProvider); final profileAsync = ref.watch(currentProfileProvider); final profile = profileAsync.valueOrNull; if (profile == null) { return Stream.value(const []); } final isAdmin = profile.role == 'admin' || profile.role == 'dispatcher'; final base = client.from('duty_schedules').stream(primaryKey: ['id']); if (isAdmin) { return base .order('start_time') .map((rows) => rows.map(DutySchedule.fromMap).toList()); } return base .eq('user_id', profile.id) .order('start_time') .map((rows) => rows.map(DutySchedule.fromMap).toList()); }); /// Fetch duty schedules by a list of IDs (used by UI when swap requests reference /// schedules that are not included in the current user's `dutySchedulesProvider`). final dutySchedulesByIdsProvider = FutureProvider.family, List>((ref, ids) async { if (ids.isEmpty) return const []; final client = ref.watch(supabaseClientProvider); final quoted = ids.map((id) => '"$id"').join(','); final inList = '($quoted)'; final rows = await client .from('duty_schedules') .select() .filter('id', 'in', inList) as List; return rows .map((r) => DutySchedule.fromMap(r as Map)) .toList(); }); /// Fetch upcoming duty schedules for a specific user (used by swap UI to /// let the requester pick a concrete target shift owned by the recipient). final dutySchedulesForUserProvider = FutureProvider.family, String>((ref, userId) async { final client = ref.watch(supabaseClientProvider); final nowIso = DateTime.now().toUtc().toIso8601String(); final rows = await client .from('duty_schedules') .select() .eq('user_id', userId) /* exclude past schedules by ensuring the shift has not ended */ .gte('end_time', nowIso) .order('start_time') as List; return rows .map((r) => DutySchedule.fromMap(r as Map)) .toList(); }); final swapRequestsProvider = StreamProvider>((ref) { final client = ref.watch(supabaseClientProvider); final profileAsync = ref.watch(currentProfileProvider); final profile = profileAsync.valueOrNull; if (profile == null) { return Stream.value(const []); } final isAdmin = profile.role == 'admin' || profile.role == 'dispatcher'; final base = client.from('swap_requests').stream(primaryKey: ['id']); if (isAdmin) { return base .order('created_at', ascending: false) .map((rows) => rows.map(SwapRequest.fromMap).toList()); } return base .order('created_at', ascending: false) .map( (rows) => rows .where( (row) => row['requester_id'] == profile.id || row['recipient_id'] == profile.id, ) .map(SwapRequest.fromMap) .toList(), ); }); final workforceControllerProvider = Provider((ref) { final client = ref.watch(supabaseClientProvider); return WorkforceController(client); }); class WorkforceController { WorkforceController(this._client); final SupabaseClient _client; Future generateSchedule({ required DateTime startDate, required DateTime endDate, }) async { await _client.rpc( 'generate_duty_schedule', params: { 'start_date': _formatDate(startDate), 'end_date': _formatDate(endDate), }, ); } Future insertSchedules(List> schedules) async { if (schedules.isEmpty) return; await _client.from('duty_schedules').insert(schedules); } Future checkIn({ required String dutyScheduleId, required double lat, required double lng, }) async { final data = await _client.rpc( 'duty_check_in', params: {'p_duty_id': dutyScheduleId, 'p_lat': lat, 'p_lng': lng}, ); return data as String?; } Future requestSwap({ required String requesterScheduleId, required String targetScheduleId, required String recipientId, }) async { final data = await _client.rpc( 'request_shift_swap', params: { 'p_shift_id': requesterScheduleId, 'p_target_shift_id': targetScheduleId, 'p_recipient_id': recipientId, }, ); return data as String?; } Future respondSwap({ required String swapId, required String action, }) async { await _client.rpc( 'respond_shift_swap', params: {'p_swap_id': swapId, 'p_action': action}, ); } /// Reassign the recipient of a swap request. Only admins/dispatchers are /// expected to call this; the DB RLS and RPCs will additionally enforce rules. Future reassignSwap({ required String swapId, required String newRecipientId, }) async { // Prefer using an RPC for server-side validation, but update directly here await _client .from('swap_requests') .update({ 'recipient_id': newRecipientId, 'status': 'pending', 'updated_at': DateTime.now().toUtc().toIso8601String(), }) .eq('id', swapId); } String _formatDate(DateTime value) { final date = DateTime(value.year, value.month, value.day); final month = date.month.toString().padLeft(2, '0'); final day = date.day.toString().padLeft(2, '0'); return '${date.year}-$month-$day'; } }