54ce528b02
- day_sheet_snapshot_builder.dart: buildSnapshotForSheet (approved → stored snapshot, else live buildDayRows), groupSheetsByDate (descending keys), approvalCounts (pending/awaiting/approved) - approvals_tab.dart: status filter chips, programmer dropdown, ReportDateFilter.controlled, date-grouped cards with Auto/Early/Round badges, checkbox multi-select, bulk Approve selected (n) flow - day_sheets_tab.dart: Today card (live rows, total, status, Submit day), Needs your attention (disapproved), history with AppStatusSummaryRow All/Pending/Disapproved/Approved filter - programmerTasksTabsFor: admin → [monitor, approvals, mine, all]; programmer → [mine, all, daySheets] - programmer_tasks_list_screen.dart: wire approvals + daySheets cases Tests: 15 passing (day_sheet_tabs_test + programmer_tabs_test) Co-Authored-By: claude-flow <ruv@ruv.net>
181 lines
6.0 KiB
Dart
181 lines
6.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:tasq/models/programmer_day_sheet.model.dart';
|
|
import 'package:tasq/providers/profile_provider.dart';
|
|
import 'package:tasq/providers/programmer_day_sheets_provider.dart';
|
|
import 'package:tasq/screens/programmer_tasks/day_sheets/approvals_tab.dart';
|
|
import 'package:tasq/screens/programmer_tasks/day_sheets/day_sheet_snapshot_builder.dart';
|
|
import 'package:tasq/theme/app_theme.dart';
|
|
import 'package:tasq/utils/app_time.dart';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
ProgrammerDaySheet _sheet({
|
|
required String id,
|
|
required String status,
|
|
DateTime? workDate,
|
|
bool autoSubmitted = false,
|
|
int resubmissions = 0,
|
|
}) {
|
|
final now = AppTime.now();
|
|
final date = workDate ?? DateTime.utc(now.year, now.month, 15);
|
|
return ProgrammerDaySheet(
|
|
id: id,
|
|
programmerId: 'prog-1',
|
|
status: status,
|
|
workDate: date,
|
|
autoSubmitted: autoSubmitted,
|
|
resubmissions: resubmissions,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
);
|
|
}
|
|
|
|
Widget _buildApp(Widget child, {List<Override> overrides = const []}) {
|
|
return ProviderScope(
|
|
overrides: overrides,
|
|
child: MaterialApp(
|
|
theme: AppTheme.light(),
|
|
home: Scaffold(body: child),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void main() {
|
|
setUpAll(AppTime.initialize);
|
|
|
|
// ── groupSheetsByDate ────────────────────────────────────────────────────
|
|
|
|
group('groupSheetsByDate', () {
|
|
test('groups by workDate, keys descending', () {
|
|
final d1 = DateTime.utc(2024, 1, 3);
|
|
final d2 = DateTime.utc(2024, 1, 2);
|
|
final d3 = DateTime.utc(2024, 1, 1);
|
|
|
|
final sheets = [
|
|
_sheet(id: 'a', status: DaySheetStatus.pending, workDate: d2),
|
|
_sheet(id: 'b', status: DaySheetStatus.approved, workDate: d1),
|
|
_sheet(id: 'c', status: DaySheetStatus.draft, workDate: d3),
|
|
_sheet(id: 'd', status: DaySheetStatus.pending, workDate: d2),
|
|
];
|
|
|
|
final grouped = groupSheetsByDate(sheets);
|
|
final keys = grouped.keys.toList();
|
|
|
|
expect(keys, [d1, d2, d3]);
|
|
expect(grouped[d1]!.map((s) => s.id).toList(), ['b']);
|
|
expect(grouped[d2]!.map((s) => s.id).toList(), ['a', 'd']);
|
|
expect(grouped[d3]!.map((s) => s.id).toList(), ['c']);
|
|
});
|
|
|
|
test('single sheet returns single-entry map', () {
|
|
final d = DateTime.utc(2024, 6, 1);
|
|
final result = groupSheetsByDate([_sheet(id: 'x', status: DaySheetStatus.pending, workDate: d)]);
|
|
expect(result.keys.toList(), [d]);
|
|
expect(result[d]!.length, 1);
|
|
});
|
|
|
|
test('empty list returns empty map', () {
|
|
expect(groupSheetsByDate([]), isEmpty);
|
|
});
|
|
});
|
|
|
|
// ── approvalCounts ───────────────────────────────────────────────────────
|
|
|
|
group('approvalCounts', () {
|
|
test('counts pending, awaiting (disapproved) and approved', () {
|
|
final sheets = [
|
|
_sheet(id: '1', status: DaySheetStatus.pending),
|
|
_sheet(id: '2', status: DaySheetStatus.pending),
|
|
_sheet(id: '3', status: DaySheetStatus.disapproved),
|
|
_sheet(id: '4', status: DaySheetStatus.approved),
|
|
_sheet(id: '5', status: DaySheetStatus.draft),
|
|
];
|
|
|
|
final counts = approvalCounts(sheets);
|
|
expect(counts.pending, 2);
|
|
expect(counts.awaiting, 1);
|
|
expect(counts.approved, 1);
|
|
});
|
|
|
|
test('all zero for empty list', () {
|
|
final counts = approvalCounts([]);
|
|
expect(counts.pending, 0);
|
|
expect(counts.awaiting, 0);
|
|
expect(counts.approved, 0);
|
|
});
|
|
|
|
test('draft sheets are not counted', () {
|
|
final counts = approvalCounts([
|
|
_sheet(id: '1', status: DaySheetStatus.draft),
|
|
_sheet(id: '2', status: DaySheetStatus.draft),
|
|
]);
|
|
expect(counts.pending, 0);
|
|
expect(counts.awaiting, 0);
|
|
expect(counts.approved, 0);
|
|
});
|
|
});
|
|
|
|
// ── ApprovalsTab widget ──────────────────────────────────────────────────
|
|
|
|
group('ApprovalsTab', () {
|
|
List<ProgrammerDaySheet> _pendingSheets(int n) {
|
|
final now = AppTime.now();
|
|
return List.generate(
|
|
n,
|
|
(i) => _sheet(
|
|
id: 'sheet-$i',
|
|
status: DaySheetStatus.pending,
|
|
workDate: DateTime.utc(now.year, now.month, 15),
|
|
),
|
|
);
|
|
}
|
|
|
|
testWidgets(
|
|
'selecting 2 of 3 pending cards shows Approve selected (2)',
|
|
(tester) async {
|
|
final sheets = _pendingSheets(3);
|
|
|
|
await tester.pumpWidget(
|
|
_buildApp(
|
|
const ApprovalsTab(),
|
|
overrides: [
|
|
allDaySheetsProvider.overrideWith(
|
|
(ref) => Stream.value(sheets),
|
|
),
|
|
profilesProvider.overrideWith(
|
|
(ref) => Stream.value([]),
|
|
),
|
|
isStrictAdminProvider.overrideWith((ref) => true),
|
|
currentUserIdProvider.overrideWith((ref) => 'admin-1'),
|
|
],
|
|
),
|
|
);
|
|
|
|
// Let the stream settle.
|
|
await tester.pump();
|
|
await tester.pump();
|
|
|
|
// All 3 pending cards should be visible with checkboxes.
|
|
final checkboxes = find.byType(Checkbox);
|
|
expect(checkboxes, findsAtLeastNWidgets(2));
|
|
|
|
// Select two cards.
|
|
await tester.tap(checkboxes.at(0));
|
|
await tester.pump();
|
|
await tester.tap(checkboxes.at(1));
|
|
await tester.pump();
|
|
|
|
// The approve button must reflect the selection count.
|
|
expect(find.text('Approve selected (2)'), findsOneWidget);
|
|
});
|
|
});
|
|
}
|