feat(programmer): sheet-driven stats, monitor status, drill-down history
- Wire daySheetsForUserProvider(userId) into ProgrammerDashboard so computeProgrammerStats/computeBadges receive real sheet data instead of the placeholder const <ProgrammerDaySheet>[]. - Monitor tab: watch allDaySheetsProvider and build sheetStatusByUser map for today; pass it to buildMonitorEntries; add "Sheets pending" chip (pending + disapproved today) to the summary row. - ProgrammerDrillDownScreen: add _RecentDaySheetsPanel below the dashboard showing the last 10 sheets (date + status pill) with tap-to-daySheetDetail. - DaySheetDetailScreen (T17 gaps): replace const [] liveRows with programmerDayDataProvider(sheet.workDate) built rows for non-approved sheets; fix _onApprove timer check to filter only the sheet programmer's own tasks (QN-1). - Add test/programmer_drill_down_test.dart (3 tests, all passing). Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:tasq/models/profile.model.dart';
|
||||
import 'package:tasq/models/programmer_day_sheet.model.dart';
|
||||
import 'package:tasq/providers/leave_provider.dart';
|
||||
import 'package:tasq/providers/programmer_day_data_provider.dart';
|
||||
import 'package:tasq/providers/programmer_day_sheets_provider.dart';
|
||||
import 'package:tasq/providers/programmer_tasks_provider.dart';
|
||||
import 'package:tasq/providers/profile_provider.dart';
|
||||
import 'package:tasq/screens/programmer_tasks/programmer_drill_down_screen.dart';
|
||||
import 'package:tasq/theme/app_theme.dart';
|
||||
import 'package:tasq/utils/app_time.dart';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
ProgrammerDaySheet _sheet({
|
||||
required String id,
|
||||
required String status,
|
||||
required DateTime workDate,
|
||||
}) {
|
||||
final now = AppTime.now();
|
||||
return ProgrammerDaySheet(
|
||||
id: id,
|
||||
programmerId: 'u1',
|
||||
status: status,
|
||||
workDate: workDate,
|
||||
autoSubmitted: false,
|
||||
resubmissions: 0,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildApp(String userId, {List<Override> overrides = const []}) {
|
||||
return ProviderScope(
|
||||
overrides: overrides,
|
||||
child: MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: ProgrammerDrillDownScreen(userId: userId),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Override> _minimalOverrides({
|
||||
List<ProgrammerDaySheet> sheets = const [],
|
||||
}) {
|
||||
final today = DateTime.utc(2026, 9, 28);
|
||||
return [
|
||||
isStrictAdminProvider.overrideWith((ref) => true),
|
||||
profilesProvider.overrideWith((ref) => Stream.value([])),
|
||||
currentProfileProvider.overrideWith((ref) => Stream.value(null)),
|
||||
programmerTasksProvider.overrideWith((ref) => Stream.value([])),
|
||||
programmerRunStatesProvider.overrideWith((ref) => const {}),
|
||||
clockTickerProvider.overrideWith((ref) => Stream.value(today)),
|
||||
todayKeyProvider.overrideWith((ref) => today),
|
||||
leavesProvider.overrideWith((ref) => Stream.value([])),
|
||||
programmerDayDataProvider.overrideWith(
|
||||
(ref, day) => Future.value(
|
||||
DayActivityData(day: day, tasks: [], logsByTask: {}, workLogs: []),
|
||||
),
|
||||
),
|
||||
daySheetsForUserProvider.overrideWith((ref, userId) => Stream.value(sheets)),
|
||||
];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void main() {
|
||||
setUpAll(AppTime.initialize);
|
||||
|
||||
group('ProgrammerDrillDownScreen — recent day sheets', () {
|
||||
testWidgets('shows Recent day sheets header when there are sheets',
|
||||
(tester) async {
|
||||
final sheets = [
|
||||
_sheet(
|
||||
id: 's1',
|
||||
status: DaySheetStatus.pending,
|
||||
workDate: DateTime.utc(2026, 9, 27),
|
||||
),
|
||||
_sheet(
|
||||
id: 's2',
|
||||
status: DaySheetStatus.approved,
|
||||
workDate: DateTime.utc(2026, 9, 26),
|
||||
),
|
||||
];
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildApp('u1', overrides: _minimalOverrides(sheets: sheets)),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
// AppSectionHeader renders title in uppercase
|
||||
expect(find.text('RECENT DAY SHEETS'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows both sheet dates', (tester) async {
|
||||
final d1 = DateTime.utc(2026, 9, 27);
|
||||
final d2 = DateTime.utc(2026, 9, 26);
|
||||
final sheets = [
|
||||
_sheet(id: 's1', status: DaySheetStatus.pending, workDate: d1),
|
||||
_sheet(id: 's2', status: DaySheetStatus.approved, workDate: d2),
|
||||
];
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildApp('u1', overrides: _minimalOverrides(sheets: sheets)),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text(AppTime.formatDate(d1)), findsOneWidget);
|
||||
expect(find.text(AppTime.formatDate(d2)), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows RECENT DAY SHEETS even with empty sheets list',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
_buildApp('u1', overrides: _minimalOverrides()),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('RECENT DAY SHEETS'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user