Fixed Leave rejection and approvals
This commit is contained in:
parent
21e6d68910
commit
f8c79acbbc
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user