Added My Schedule tab in attendance screen
Allow 1 Day, Whole Week and Date Range swapping
This commit is contained in:
@@ -78,6 +78,7 @@ class PassSlipController {
|
||||
Future<void> requestSlip({
|
||||
required String dutyScheduleId,
|
||||
required String reason,
|
||||
DateTime? requestedStart,
|
||||
}) async {
|
||||
final userId = _client.auth.currentUser?.id;
|
||||
if (userId == null) throw Exception('Not authenticated');
|
||||
@@ -87,6 +88,8 @@ class PassSlipController {
|
||||
'reason': reason,
|
||||
'status': 'pending',
|
||||
'requested_at': DateTime.now().toUtc().toIso8601String(),
|
||||
if (requestedStart != null)
|
||||
'requested_start': requestedStart.toUtc().toIso8601String(),
|
||||
};
|
||||
|
||||
final insertedRaw = await _client
|
||||
@@ -174,13 +177,29 @@ class PassSlipController {
|
||||
final userId = _client.auth.currentUser?.id;
|
||||
if (userId == null) throw Exception('Not authenticated');
|
||||
|
||||
// Determine slip start time based on requested_start
|
||||
final nowUtc = DateTime.now().toUtc();
|
||||
String slipStartIso = nowUtc.toIso8601String();
|
||||
|
||||
final row = await _client
|
||||
.from('pass_slips')
|
||||
.select('requested_start')
|
||||
.eq('id', slipId)
|
||||
.maybeSingle();
|
||||
if (row != null && row['requested_start'] != null) {
|
||||
final requestedStart = DateTime.parse(row['requested_start'] as String);
|
||||
if (requestedStart.isAfter(nowUtc)) {
|
||||
slipStartIso = requestedStart.toIso8601String();
|
||||
}
|
||||
}
|
||||
|
||||
await _client
|
||||
.from('pass_slips')
|
||||
.update({
|
||||
'status': 'approved',
|
||||
'approved_by': userId,
|
||||
'approved_at': DateTime.now().toUtc().toIso8601String(),
|
||||
'slip_start': DateTime.now().toUtc().toIso8601String(),
|
||||
'approved_at': nowUtc.toIso8601String(),
|
||||
'slip_start': slipStartIso,
|
||||
})
|
||||
.eq('id', slipId);
|
||||
|
||||
|
||||
@@ -344,6 +344,11 @@ class StreamRecoveryWrapper<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Immediately fetch fresh data via REST without restarting the realtime
|
||||
/// subscription. Use this as a periodic safety net for missed realtime events
|
||||
/// (e.g., when the table is not yet in the supabase_realtime publication).
|
||||
Future<void> pollNow() async => _pollOnce();
|
||||
|
||||
/// Manually trigger a recovery attempt.
|
||||
void retry() {
|
||||
_recoveryAttempts = 0;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
@@ -53,6 +55,20 @@ final dutySchedulesProvider = StreamProvider<List<DutySchedule>>((ref) {
|
||||
);
|
||||
|
||||
ref.onDispose(wrapper.dispose);
|
||||
|
||||
// Immediate poll so any changes that happened while this provider was
|
||||
// not alive (e.g. a swap was accepted on another device) are reflected
|
||||
// right away — before the 3-second periodic timer fires.
|
||||
wrapper.pollNow();
|
||||
|
||||
// Periodic safety-net: keep polling every 3 s so that ownership changes
|
||||
// (swap accepted → user_id updated on duty_schedules) are always picked
|
||||
// up even if Supabase Realtime misses the event.
|
||||
final dutyRefreshTimer = Timer.periodic(const Duration(seconds: 3), (_) {
|
||||
wrapper.pollNow();
|
||||
});
|
||||
ref.onDispose(dutyRefreshTimer.cancel);
|
||||
|
||||
return wrapper.stream.map((result) => result.data);
|
||||
});
|
||||
|
||||
@@ -127,6 +143,7 @@ final swapRequestsProvider = StreamProvider<List<SwapRequest>>((ref) {
|
||||
final data = await client
|
||||
.from('swap_requests')
|
||||
.select()
|
||||
.inFilter('status', ['pending', 'admin_review'])
|
||||
.order('created_at', ascending: false);
|
||||
return data.map(SwapRequest.fromMap).toList();
|
||||
},
|
||||
@@ -136,13 +153,28 @@ final swapRequestsProvider = StreamProvider<List<SwapRequest>>((ref) {
|
||||
);
|
||||
|
||||
ref.onDispose(wrapper.dispose);
|
||||
|
||||
// Immediate poll: fetch fresh data right away so any status changes that
|
||||
// happened while this provider was not alive are reflected instantly,
|
||||
// before the 3-second periodic timer fires for the first time.
|
||||
wrapper.pollNow();
|
||||
|
||||
// Periodic safety-net: keep polling every 3 s to catch any status changes
|
||||
// that Supabase Realtime may have missed (e.g. when the swap_requests table
|
||||
// is not yet in the supabase_realtime publication).
|
||||
final refreshTimer = Timer.periodic(const Duration(seconds: 3), (_) {
|
||||
wrapper.pollNow();
|
||||
});
|
||||
ref.onDispose(refreshTimer.cancel);
|
||||
|
||||
return wrapper.stream.map((result) {
|
||||
// only return requests that are still actionable; once a swap has been
|
||||
// accepted or rejected we no longer need to bubble it up to the UI for
|
||||
// either party. admins still see "admin_review" rows so they can act on
|
||||
// escalated cases.
|
||||
return result.data.where((row) {
|
||||
if (!(row.requesterId == profileId || row.recipientId == profileId)) {
|
||||
// admins see all swaps; standard users only see swaps they're in
|
||||
if (!isAdmin && !(row.requesterId == profileId || row.recipientId == profileId)) {
|
||||
return false;
|
||||
}
|
||||
// only keep pending and admin_review statuses
|
||||
@@ -151,6 +183,20 @@ final swapRequestsProvider = StreamProvider<List<SwapRequest>>((ref) {
|
||||
});
|
||||
});
|
||||
|
||||
/// IDs of swap requests that were acted on locally (accepted, rejected, etc.).
|
||||
/// Kept as a global provider so the set survives tab switches — widget state
|
||||
/// is disposed when the user navigates away from My Schedule.
|
||||
final locallyRemovedSwapIdsProvider = StateProvider<Set<String>>((ref) => {});
|
||||
|
||||
/// IDs of duty_schedules owned by the current user that were created by an accepted swap.
|
||||
final swappedScheduleIdsProvider = Provider<Set<String>>((ref) {
|
||||
final schedules = ref.watch(dutySchedulesProvider).valueOrNull ?? [];
|
||||
return {
|
||||
for (final s in schedules)
|
||||
if (s.swapRequestId != null) s.id,
|
||||
};
|
||||
});
|
||||
|
||||
final workforceControllerProvider = Provider<WorkforceController>((ref) {
|
||||
final client = ref.watch(supabaseClientProvider);
|
||||
return WorkforceController(client);
|
||||
|
||||
Reference in New Issue
Block a user