refactor: extract staff presence helpers
- Extract activeLeaveByUser, activePassSlipByUser, and approvedLeaveDays to staff_presence.dart utility module - Replace inline indexing loops in dashboard_screen.dart with helper function calls - Behavior unchanged, tests added covering all functions Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -44,6 +44,7 @@ import '../../widgets/app_metric_card.dart';
|
|||||||
import '../../widgets/app_page_header.dart';
|
import '../../widgets/app_page_header.dart';
|
||||||
import '../../widgets/app_section_header.dart';
|
import '../../widgets/app_section_header.dart';
|
||||||
import '../../utils/app_time.dart';
|
import '../../utils/app_time.dart';
|
||||||
|
import '../../utils/staff_presence.dart' as staff_presence;
|
||||||
|
|
||||||
class DashboardMetrics {
|
class DashboardMetrics {
|
||||||
DashboardMetrics({
|
DashboardMetrics({
|
||||||
@@ -369,22 +370,10 @@ final dashboardMetricsProvider = Provider<AsyncValue<DashboardMetrics>>((ref) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Index today's leaves by user.
|
// Index today's leaves by user.
|
||||||
final todayLeaveByUser = <String, LeaveOfAbsence>{};
|
final todayLeaveByUser = staff_presence.activeLeaveByUser(allLeaves, now);
|
||||||
for (final l in allLeaves) {
|
|
||||||
if (l.status == 'approved' &&
|
|
||||||
!l.startTime.isAfter(now) &&
|
|
||||||
l.endTime.isAfter(now)) {
|
|
||||||
todayLeaveByUser[l.userId] = l;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index active pass slips by user.
|
// Index active pass slips by user.
|
||||||
final activePassSlipByUser = <String, PassSlip>{};
|
final activePassSlipByUser = staff_presence.activePassSlipByUser(allPassSlips);
|
||||||
for (final slip in allPassSlips) {
|
|
||||||
if (slip.isActive) {
|
|
||||||
activePassSlipByUser[slip.userId] = slip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final noon = DateTime(now.year, now.month, now.day, 12, 0);
|
final noon = DateTime(now.year, now.month, now.day, 12, 0);
|
||||||
final onePM = DateTime(now.year, now.month, now.day, 13, 0);
|
final onePM = DateTime(now.year, now.month, now.day, 13, 0);
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import 'package:tasq/models/leave_of_absence.model.dart';
|
||||||
|
import 'package:tasq/models/pass_slip.model.dart';
|
||||||
|
import 'programmer_daily_time.dart';
|
||||||
|
|
||||||
|
/// Index approved leaves covering the given moment by user ID.
|
||||||
|
///
|
||||||
|
/// Filters to leaves where:
|
||||||
|
/// - status == 'approved'
|
||||||
|
/// - startTime <= now < endTime
|
||||||
|
///
|
||||||
|
/// Returns a map from userId to the leave record.
|
||||||
|
Map<String, LeaveOfAbsence> activeLeaveByUser(
|
||||||
|
List<LeaveOfAbsence> leaves,
|
||||||
|
DateTime now,
|
||||||
|
) {
|
||||||
|
final result = <String, LeaveOfAbsence>{};
|
||||||
|
for (final leave in leaves) {
|
||||||
|
if (leave.status == 'approved' &&
|
||||||
|
!leave.startTime.isAfter(now) &&
|
||||||
|
leave.endTime.isAfter(now)) {
|
||||||
|
result[leave.userId] = leave;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Index active pass slips by user ID.
|
||||||
|
///
|
||||||
|
/// Filters to slips where slip.isActive is true.
|
||||||
|
///
|
||||||
|
/// Returns a map from userId to the pass slip record.
|
||||||
|
Map<String, PassSlip> activePassSlipByUser(List<PassSlip> slips) {
|
||||||
|
final result = <String, PassSlip>{};
|
||||||
|
for (final slip in slips) {
|
||||||
|
if (slip.isActive) {
|
||||||
|
result[slip.userId] = slip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the set of Manila calendar days covered by approved leaves for a user.
|
||||||
|
///
|
||||||
|
/// Iterates through all approved leaves for the given user and collects the
|
||||||
|
/// set of Manila day keys (as DateTime.utc(year, month, day)) from each
|
||||||
|
/// leave's start date through end date, inclusive.
|
||||||
|
Set<DateTime> approvedLeaveDays(
|
||||||
|
List<LeaveOfAbsence> leaves,
|
||||||
|
String userId,
|
||||||
|
) {
|
||||||
|
final result = <DateTime>{};
|
||||||
|
for (final leave in leaves) {
|
||||||
|
if (leave.userId == userId && leave.status == 'approved') {
|
||||||
|
var current = manilaDay(leave.startTime);
|
||||||
|
final endDay = manilaDay(leave.endTime);
|
||||||
|
while (!current.isAfter(endDay)) {
|
||||||
|
result.add(current);
|
||||||
|
current = current.add(Duration(days: 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:tasq/models/leave_of_absence.model.dart';
|
||||||
|
import 'package:tasq/models/pass_slip.model.dart';
|
||||||
|
import 'package:tasq/utils/app_time.dart';
|
||||||
|
import 'package:tasq/utils/staff_presence.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
setUpAll(() {
|
||||||
|
AppTime.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('activeLeaveByUser', () {
|
||||||
|
test('includes approved leave covering now', () {
|
||||||
|
final now = AppTime.now();
|
||||||
|
final startTime = now.subtract(Duration(hours: 1));
|
||||||
|
final endTime = now.add(Duration(hours: 1));
|
||||||
|
|
||||||
|
final leave = LeaveOfAbsence(
|
||||||
|
id: 'leave1',
|
||||||
|
userId: 'user1',
|
||||||
|
leaveType: 'sick_leave',
|
||||||
|
justification: 'Sick',
|
||||||
|
startTime: startTime,
|
||||||
|
endTime: endTime,
|
||||||
|
status: 'approved',
|
||||||
|
filedBy: 'admin',
|
||||||
|
createdAt: now,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = activeLeaveByUser([leave], now);
|
||||||
|
expect(result, containsPair('user1', leave));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('excludes pending leave', () {
|
||||||
|
final now = AppTime.now();
|
||||||
|
final startTime = now.subtract(Duration(hours: 1));
|
||||||
|
final endTime = now.add(Duration(hours: 1));
|
||||||
|
|
||||||
|
final leave = LeaveOfAbsence(
|
||||||
|
id: 'leave1',
|
||||||
|
userId: 'user1',
|
||||||
|
leaveType: 'sick_leave',
|
||||||
|
justification: 'Sick',
|
||||||
|
startTime: startTime,
|
||||||
|
endTime: endTime,
|
||||||
|
status: 'pending',
|
||||||
|
filedBy: 'admin',
|
||||||
|
createdAt: now,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = activeLeaveByUser([leave], now);
|
||||||
|
expect(result, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('excludes leave that ended before now', () {
|
||||||
|
final now = AppTime.now();
|
||||||
|
final startTime = now.subtract(Duration(hours: 3));
|
||||||
|
final endTime = now.subtract(Duration(hours: 1));
|
||||||
|
|
||||||
|
final leave = LeaveOfAbsence(
|
||||||
|
id: 'leave1',
|
||||||
|
userId: 'user1',
|
||||||
|
leaveType: 'sick_leave',
|
||||||
|
justification: 'Sick',
|
||||||
|
startTime: startTime,
|
||||||
|
endTime: endTime,
|
||||||
|
status: 'approved',
|
||||||
|
filedBy: 'admin',
|
||||||
|
createdAt: now,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = activeLeaveByUser([leave], now);
|
||||||
|
expect(result, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('excludes leave that starts after now', () {
|
||||||
|
final now = AppTime.now();
|
||||||
|
final startTime = now.add(Duration(hours: 1));
|
||||||
|
final endTime = now.add(Duration(hours: 3));
|
||||||
|
|
||||||
|
final leave = LeaveOfAbsence(
|
||||||
|
id: 'leave1',
|
||||||
|
userId: 'user1',
|
||||||
|
leaveType: 'sick_leave',
|
||||||
|
justification: 'Sick',
|
||||||
|
startTime: startTime,
|
||||||
|
endTime: endTime,
|
||||||
|
status: 'approved',
|
||||||
|
filedBy: 'admin',
|
||||||
|
createdAt: now,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = activeLeaveByUser([leave], now);
|
||||||
|
expect(result, isEmpty);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('activePassSlipByUser', () {
|
||||||
|
test('includes active pass slip', () {
|
||||||
|
final slip = PassSlip(
|
||||||
|
id: 'slip1',
|
||||||
|
userId: 'user1',
|
||||||
|
dutyScheduleId: 'sched1',
|
||||||
|
reason: 'Test',
|
||||||
|
status: 'approved',
|
||||||
|
requestedAt: AppTime.now(),
|
||||||
|
slipStart: AppTime.now(),
|
||||||
|
slipEnd: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = activePassSlipByUser([slip]);
|
||||||
|
expect(result, containsPair('user1', slip));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('excludes pending pass slip', () {
|
||||||
|
final slip = PassSlip(
|
||||||
|
id: 'slip1',
|
||||||
|
userId: 'user1',
|
||||||
|
dutyScheduleId: 'sched1',
|
||||||
|
reason: 'Test',
|
||||||
|
status: 'pending',
|
||||||
|
requestedAt: AppTime.now(),
|
||||||
|
slipStart: null,
|
||||||
|
slipEnd: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = activePassSlipByUser([slip]);
|
||||||
|
expect(result, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('excludes ended pass slip', () {
|
||||||
|
final slip = PassSlip(
|
||||||
|
id: 'slip1',
|
||||||
|
userId: 'user1',
|
||||||
|
dutyScheduleId: 'sched1',
|
||||||
|
reason: 'Test',
|
||||||
|
status: 'approved',
|
||||||
|
requestedAt: AppTime.now(),
|
||||||
|
slipStart: AppTime.now(),
|
||||||
|
slipEnd: AppTime.now().add(Duration(hours: 1)),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = activePassSlipByUser([slip]);
|
||||||
|
expect(result, isEmpty);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('approvedLeaveDays', () {
|
||||||
|
test('single day leave returns one day key', () {
|
||||||
|
// Sep 24, 08:00 to Sep 24, 17:00 Manila time → just Sep 24
|
||||||
|
final start = AppTime.fromComponents(year: 2026, month: 9, day: 24, hour: 8, minute: 0);
|
||||||
|
final end = AppTime.fromComponents(year: 2026, month: 9, day: 24, hour: 17, minute: 0);
|
||||||
|
|
||||||
|
final leave = LeaveOfAbsence(
|
||||||
|
id: 'leave1',
|
||||||
|
userId: 'user1',
|
||||||
|
leaveType: 'sick_leave',
|
||||||
|
justification: 'Sick',
|
||||||
|
startTime: start,
|
||||||
|
endTime: end,
|
||||||
|
status: 'approved',
|
||||||
|
filedBy: 'admin',
|
||||||
|
createdAt: DateTime.utc(2026, 9, 23),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = approvedLeaveDays([leave], 'user1');
|
||||||
|
expect(result, contains(DateTime.utc(2026, 9, 24)));
|
||||||
|
expect(result.length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multi-day leave returns all day keys', () {
|
||||||
|
// Sep 24, 08:00 to Sep 25, 17:00 Manila time → Sep 24 and Sep 25
|
||||||
|
final start = AppTime.fromComponents(year: 2026, month: 9, day: 24, hour: 8, minute: 0);
|
||||||
|
final end = AppTime.fromComponents(year: 2026, month: 9, day: 25, hour: 17, minute: 0);
|
||||||
|
|
||||||
|
final leave = LeaveOfAbsence(
|
||||||
|
id: 'leave1',
|
||||||
|
userId: 'user1',
|
||||||
|
leaveType: 'vacation_leave',
|
||||||
|
justification: 'Vacation',
|
||||||
|
startTime: start,
|
||||||
|
endTime: end,
|
||||||
|
status: 'approved',
|
||||||
|
filedBy: 'admin',
|
||||||
|
createdAt: DateTime.utc(2026, 9, 23),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = approvedLeaveDays([leave], 'user1');
|
||||||
|
expect(result, contains(DateTime.utc(2026, 9, 24)));
|
||||||
|
expect(result, contains(DateTime.utc(2026, 9, 25)));
|
||||||
|
expect(result.length, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('excludes pending leaves', () {
|
||||||
|
final start = AppTime.fromComponents(year: 2026, month: 9, day: 24, hour: 8, minute: 0);
|
||||||
|
final end = AppTime.fromComponents(year: 2026, month: 9, day: 25, hour: 17, minute: 0);
|
||||||
|
|
||||||
|
final leave = LeaveOfAbsence(
|
||||||
|
id: 'leave1',
|
||||||
|
userId: 'user1',
|
||||||
|
leaveType: 'vacation_leave',
|
||||||
|
justification: 'Vacation',
|
||||||
|
startTime: start,
|
||||||
|
endTime: end,
|
||||||
|
status: 'pending',
|
||||||
|
filedBy: 'admin',
|
||||||
|
createdAt: DateTime.utc(2026, 9, 23),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = approvedLeaveDays([leave], 'user1');
|
||||||
|
expect(result, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('filters by userId', () {
|
||||||
|
final start = AppTime.fromComponents(year: 2026, month: 9, day: 24, hour: 8, minute: 0);
|
||||||
|
final end = AppTime.fromComponents(year: 2026, month: 9, day: 25, hour: 17, minute: 0);
|
||||||
|
|
||||||
|
final leave = LeaveOfAbsence(
|
||||||
|
id: 'leave1',
|
||||||
|
userId: 'user1',
|
||||||
|
leaveType: 'vacation_leave',
|
||||||
|
justification: 'Vacation',
|
||||||
|
startTime: start,
|
||||||
|
endTime: end,
|
||||||
|
status: 'approved',
|
||||||
|
filedBy: 'admin',
|
||||||
|
createdAt: DateTime.utc(2026, 9, 23),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = approvedLeaveDays([leave], 'user2');
|
||||||
|
expect(result, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles multiple approved leaves for same user', () {
|
||||||
|
final leave1 = LeaveOfAbsence(
|
||||||
|
id: 'leave1',
|
||||||
|
userId: 'user1',
|
||||||
|
leaveType: 'sick_leave',
|
||||||
|
justification: 'Sick',
|
||||||
|
startTime: AppTime.fromComponents(year: 2026, month: 9, day: 24, hour: 8, minute: 0),
|
||||||
|
endTime: AppTime.fromComponents(year: 2026, month: 9, day: 24, hour: 17, minute: 0),
|
||||||
|
status: 'approved',
|
||||||
|
filedBy: 'admin',
|
||||||
|
createdAt: AppTime.fromComponents(year: 2026, month: 9, day: 23),
|
||||||
|
);
|
||||||
|
|
||||||
|
final leave2 = LeaveOfAbsence(
|
||||||
|
id: 'leave2',
|
||||||
|
userId: 'user1',
|
||||||
|
leaveType: 'vacation_leave',
|
||||||
|
justification: 'Vacation',
|
||||||
|
startTime: AppTime.fromComponents(year: 2026, month: 9, day: 26, hour: 8, minute: 0),
|
||||||
|
endTime: AppTime.fromComponents(year: 2026, month: 9, day: 27, hour: 17, minute: 0),
|
||||||
|
status: 'approved',
|
||||||
|
filedBy: 'admin',
|
||||||
|
createdAt: AppTime.fromComponents(year: 2026, month: 9, day: 23),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = approvedLeaveDays([leave1, leave2], 'user1');
|
||||||
|
expect(result, contains(DateTime.utc(2026, 9, 24)));
|
||||||
|
expect(result, contains(DateTime.utc(2026, 9, 26)));
|
||||||
|
expect(result, contains(DateTime.utc(2026, 9, 27)));
|
||||||
|
expect(result.length, 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user