Fixed Leave rejection and approvals

This commit is contained in:
Marc Rejohn Castillano 2026-03-11 18:59:28 +08:00
parent 21e6d68910
commit f8c79acbbc
4 changed files with 41 additions and 9 deletions

View File

@ -8,6 +8,12 @@ import 'stream_recovery.dart';
import 'realtime_controller.dart';
/// All visible leaves (own for standard, all for admin/dispatcher/it_staff).
///
/// Consumers should **not** treat every record as an active absence; the UI
/// layers (dashboard, logbook) explicitly filter to `status == 'approved'` and
/// verify the leave overlaps the current time. This prevents rejected or
/// pending applications from inadvertently influencing schedules or status
/// computations.
final leavesProvider = StreamProvider<List<LeaveOfAbsence>>((ref) {
final client = ref.watch(supabaseClientProvider);
final profileAsync = ref.watch(currentProfileProvider);

View File

@ -127,12 +127,17 @@ final swapRequestsProvider = StreamProvider<List<SwapRequest>>((ref) {
ref.onDispose(wrapper.dispose);
return wrapper.stream.map((result) {
return result.data
.where(
(row) =>
row.requesterId == profile.id || row.recipientId == profile.id,
)
.toList();
// 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 == profile.id || row.recipientId == profile.id)) {
return false;
}
// only keep pending and admin_review statuses
return row.status == 'pending' || row.status == 'admin_review';
}).toList();
});
});

View File

@ -16,6 +16,7 @@ import '../../models/profile.dart';
import '../../providers/attendance_provider.dart';
import '../../providers/debug_settings_provider.dart';
import '../../providers/leave_provider.dart';
import '../../screens/dashboard/dashboard_screen.dart';
import '../../providers/pass_slip_provider.dart';
import '../../providers/profile_provider.dart';
import '../../providers/reports_provider.dart';
@ -3796,6 +3797,8 @@ class _LeaveTabState extends ConsumerState<_LeaveTab> {
setState(() => _submitting = true);
try {
await ref.read(leaveControllerProvider).approveLeave(leaveId);
ref.invalidate(leavesProvider);
ref.invalidate(dashboardMetricsProvider);
if (mounted) {
showSuccessSnackBar(context, 'Leave approved.');
}
@ -3812,6 +3815,8 @@ class _LeaveTabState extends ConsumerState<_LeaveTab> {
setState(() => _submitting = true);
try {
await ref.read(leaveControllerProvider).rejectLeave(leaveId);
ref.invalidate(leavesProvider);
ref.invalidate(dashboardMetricsProvider);
if (mounted) {
showSuccessSnackBar(context, 'Leave rejected.');
}
@ -3828,6 +3833,8 @@ class _LeaveTabState extends ConsumerState<_LeaveTab> {
setState(() => _submitting = true);
try {
await ref.read(leaveControllerProvider).cancelLeave(leaveId);
ref.invalidate(leavesProvider);
ref.invalidate(dashboardMetricsProvider);
if (mounted) {
showSuccessSnackBar(context, 'Leave cancelled.');
}
@ -4112,14 +4119,14 @@ class _FileLeaveDialogState extends ConsumerState<_FileLeaveDialog> {
return;
}
final startDt = DateTime(
var startDt = DateTime(
_startDate!.year,
_startDate!.month,
_startDate!.day,
_startTime!.hour,
_startTime!.minute,
);
final endDt = DateTime(
var endDt = DateTime(
_startDate!.year,
_startDate!.month,
_startDate!.day,
@ -4132,6 +4139,10 @@ class _FileLeaveDialogState extends ConsumerState<_FileLeaveDialog> {
return;
}
// convert to app timezone to avoid device-local mismatches
startDt = AppTime.toAppTime(startDt);
endDt = AppTime.toAppTime(endDt);
setState(() => _submitting = true);
try {
await ref
@ -4143,6 +4154,10 @@ class _FileLeaveDialogState extends ConsumerState<_FileLeaveDialog> {
endTime: endDt,
autoApprove: widget.isAdmin,
);
// ensure UI and dashboard will refresh promptly even if realtime is
// delayed or temporarily disconnected
ref.invalidate(leavesProvider);
ref.invalidate(dashboardMetricsProvider);
if (mounted) {
Navigator.of(context).pop();
widget.onSubmitted();

View File

@ -513,7 +513,7 @@ class _ScheduleTile extends ConsumerWidget {
if (confirmed != true || !context.mounted) return;
final startDateTime = DateTime(
var startDateTime = DateTime(
selectedDate.year,
selectedDate.month,
selectedDate.day,
@ -531,6 +531,12 @@ class _ScheduleTile extends ConsumerWidget {
endDateTime = endDateTime.add(const Duration(days: 1));
}
// ensure times are expressed in the app timezone (Asia/Manila) before
// sending to the backend. previously these were raw local DateTimes which
// caused off-by-offset errors when the device timezone differed.
startDateTime = AppTime.toAppTime(startDateTime);
endDateTime = AppTime.toAppTime(endDateTime);
try {
await ref
.read(workforceControllerProvider)