Initial Commit: Duty Schedule and Attendance Logbook
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import '../models/pass_slip.dart';
|
||||
import 'profile_provider.dart';
|
||||
import 'supabase_provider.dart';
|
||||
import 'stream_recovery.dart';
|
||||
import 'realtime_controller.dart';
|
||||
|
||||
/// All visible pass slips (own for staff, all for admin/dispatcher).
|
||||
final passSlipsProvider = StreamProvider<List<PassSlip>>((ref) {
|
||||
final client = ref.watch(supabaseClientProvider);
|
||||
final profileAsync = ref.watch(currentProfileProvider);
|
||||
final profile = profileAsync.valueOrNull;
|
||||
if (profile == null) return Stream.value(const <PassSlip>[]);
|
||||
|
||||
final isAdmin = profile.role == 'admin' || profile.role == 'dispatcher';
|
||||
|
||||
final wrapper = StreamRecoveryWrapper<PassSlip>(
|
||||
stream: isAdmin
|
||||
? client
|
||||
.from('pass_slips')
|
||||
.stream(primaryKey: ['id'])
|
||||
.order('requested_at', ascending: false)
|
||||
: client
|
||||
.from('pass_slips')
|
||||
.stream(primaryKey: ['id'])
|
||||
.eq('user_id', profile.id)
|
||||
.order('requested_at', ascending: false),
|
||||
onPollData: () async {
|
||||
final query = client.from('pass_slips').select();
|
||||
final data = isAdmin
|
||||
? await query.order('requested_at', ascending: false)
|
||||
: await query
|
||||
.eq('user_id', profile.id)
|
||||
.order('requested_at', ascending: false);
|
||||
return data.map(PassSlip.fromMap).toList();
|
||||
},
|
||||
fromMap: PassSlip.fromMap,
|
||||
channelName: 'pass_slips',
|
||||
onStatusChanged: ref.read(realtimeControllerProvider).handleChannelStatus,
|
||||
);
|
||||
|
||||
ref.onDispose(wrapper.dispose);
|
||||
return wrapper.stream.map((result) => result.data);
|
||||
});
|
||||
|
||||
/// Currently active pass slip for the logged-in user (approved, not completed).
|
||||
final activePassSlipProvider = Provider<PassSlip?>((ref) {
|
||||
final slips = ref.watch(passSlipsProvider).valueOrNull ?? [];
|
||||
final userId = ref.watch(currentUserIdProvider);
|
||||
if (userId == null) return null;
|
||||
try {
|
||||
return slips.firstWhere((s) => s.userId == userId && s.isActive);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
/// Active pass slips for all users (for dashboard IT Staff Pulse).
|
||||
final activePassSlipsProvider = Provider<List<PassSlip>>((ref) {
|
||||
final slips = ref.watch(passSlipsProvider).valueOrNull ?? [];
|
||||
return slips.where((s) => s.isActive).toList();
|
||||
});
|
||||
|
||||
final passSlipControllerProvider = Provider<PassSlipController>((ref) {
|
||||
final client = ref.watch(supabaseClientProvider);
|
||||
return PassSlipController(client);
|
||||
});
|
||||
|
||||
class PassSlipController {
|
||||
PassSlipController(this._client);
|
||||
|
||||
final SupabaseClient _client;
|
||||
|
||||
Future<void> requestSlip({
|
||||
required String dutyScheduleId,
|
||||
required String reason,
|
||||
}) async {
|
||||
final userId = _client.auth.currentUser?.id;
|
||||
if (userId == null) throw Exception('Not authenticated');
|
||||
await _client.from('pass_slips').insert({
|
||||
'user_id': userId,
|
||||
'duty_schedule_id': dutyScheduleId,
|
||||
'reason': reason,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> approveSlip(String slipId) async {
|
||||
final userId = _client.auth.currentUser?.id;
|
||||
if (userId == null) throw Exception('Not authenticated');
|
||||
await _client
|
||||
.from('pass_slips')
|
||||
.update({
|
||||
'status': 'approved',
|
||||
'approved_by': userId,
|
||||
'approved_at': DateTime.now().toUtc().toIso8601String(),
|
||||
'slip_start': DateTime.now().toUtc().toIso8601String(),
|
||||
})
|
||||
.eq('id', slipId);
|
||||
}
|
||||
|
||||
Future<void> rejectSlip(String slipId) async {
|
||||
await _client
|
||||
.from('pass_slips')
|
||||
.update({
|
||||
'status': 'rejected',
|
||||
'approved_by': _client.auth.currentUser?.id,
|
||||
'approved_at': DateTime.now().toUtc().toIso8601String(),
|
||||
})
|
||||
.eq('id', slipId);
|
||||
}
|
||||
|
||||
Future<void> completeSlip(String slipId) async {
|
||||
await _client
|
||||
.from('pass_slips')
|
||||
.update({
|
||||
'status': 'completed',
|
||||
'slip_end': DateTime.now().toUtc().toIso8601String(),
|
||||
})
|
||||
.eq('id', slipId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user