diff --git a/lib/src/router.dart b/lib/src/router.dart index d9b5b3b..595e059 100644 --- a/lib/src/router.dart +++ b/lib/src/router.dart @@ -6,9 +6,8 @@ import 'data/auth/auth_controller.dart'; import 'ui/dop_picker/dop_picker_screen.dart'; import 'ui/driver/driver_home_screen.dart'; import 'ui/driver/trip_detail_screen.dart'; -import 'ui/helper/helper_home_screen.dart'; import 'ui/helper/helper_trip_detail_screen.dart'; -import 'ui/home/home_screen.dart'; +import 'ui/home/crew_home_screen.dart'; import 'ui/login/login_screen.dart'; import 'ui/scan/scan_camera_screen.dart'; import 'ui/tenant/tenant_screen.dart'; @@ -41,8 +40,8 @@ final routerProvider = Provider((ref) { } if (auth.user.role == 'helper') { - if (loc == '/login' || loc == '/tenant' || loc == '/' || loc == '/dop' || loc == '/home' || loc == '/scan' || loc.startsWith('/driver/')) { - return '/helper/home'; + if (loc == '/login' || loc == '/tenant' || loc == '/' || loc == '/dop' || loc == '/scan' || loc.startsWith('/driver/')) { + return '/home'; } return null; } @@ -51,7 +50,7 @@ final routerProvider = Provider((ref) { if (loc == '/scan' && !auth.hasActiveDop) { return '/home'; } - if (loc == '/login' || loc == '/tenant' || loc == '/' || loc == '/dop' || loc.startsWith('/driver/') || loc == '/helper/home') { + if (loc == '/login' || loc == '/tenant' || loc == '/' || loc == '/dop' || loc.startsWith('/driver/')) { return '/home'; } return null; @@ -64,10 +63,9 @@ final routerProvider = Provider((ref) { GoRoute(path: '/tenant', builder: (c, s) => const TenantScreen()), GoRoute(path: '/login', builder: (c, s) => const LoginScreen()), GoRoute(path: '/dop', builder: (c, s) => const DopPickerScreen()), - GoRoute(path: '/home', builder: (c, s) => const HomeScreen()), + GoRoute(path: '/home', builder: (c, s) => const CrewHomeScreen()), GoRoute(path: '/scan', builder: (c, s) => const ScanCameraScreen()), GoRoute(path: '/driver/home', builder: (c, s) => const DriverHomeScreen()), - GoRoute(path: '/helper/home', builder: (c, s) => const HelperHomeScreen()), GoRoute(path: '/helper/active-trip', builder: (c, s) => const HelperTripDetailScreen()), GoRoute( path: '/driver/trips/:uuid', diff --git a/lib/src/ui/helper/helper_home_screen.dart b/lib/src/ui/helper/helper_home_screen.dart index b24e1c6..7d3c14d 100644 --- a/lib/src/ui/helper/helper_home_screen.dart +++ b/lib/src/ui/helper/helper_home_screen.dart @@ -2,43 +2,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import '../../data/auth/auth_controller.dart'; import '../../data/helper/helper_models.dart'; import '../../data/helper/helper_providers.dart'; import '../../theme/app_theme.dart'; -class HelperHomeScreen extends ConsumerWidget { - const HelperHomeScreen({super.key}); +class HelperAssignmentTab extends ConsumerWidget { + const HelperAssignmentTab({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final auth = ref.watch(authControllerProvider); - if (auth is! Authenticated) return const SizedBox.shrink(); - final assignmentState = ref.watch(currentAssignmentProvider); final historyState = ref.watch(helperHistoryProvider); - return Scaffold( - appBar: AppBar( - titleSpacing: 16, - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Hi, ${auth.user.fullName.split(' ').first}', - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700)), - Text('${auth.tenantName} · Helper', - style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF737373))), - ], - ), - actions: [ - IconButton( - tooltip: 'Sign out', - icon: const Icon(Icons.logout_rounded), - onPressed: () => ref.read(authControllerProvider.notifier).logout(), - ), - ], - ), - body: SafeArea( + return SafeArea( child: RefreshIndicator( onRefresh: () async { ref.read(currentAssignmentProvider.notifier).refresh(); @@ -114,8 +90,7 @@ class HelperHomeScreen extends ConsumerWidget { ], ), ), - ), - ); + ); } } diff --git a/lib/src/ui/home/crew_home_screen.dart b/lib/src/ui/home/crew_home_screen.dart new file mode 100644 index 0000000..c3d4949 --- /dev/null +++ b/lib/src/ui/home/crew_home_screen.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../../data/auth/auth_controller.dart'; +import '../helper/helper_home_screen.dart'; +import 'home_screen.dart'; +import '../../theme/app_theme.dart'; + +class CrewHomeScreen extends ConsumerStatefulWidget { + const CrewHomeScreen({super.key}); + + @override + ConsumerState createState() => _CrewHomeScreenState(); +} + +class _CrewHomeScreenState extends ConsumerState { + int _currentIndex = 0; + + @override + Widget build(BuildContext context) { + final auth = ref.watch(authControllerProvider); + if (auth is! Authenticated) return const SizedBox.shrink(); + + final isScanner = auth.user.role == 'scanner' || auth.user.role == 'admin'; + + final tabs = [ + if (isScanner) + const BottomNavigationBarItem(icon: Icon(Icons.qr_code_scanner_rounded), label: 'Scan'), + const BottomNavigationBarItem(icon: Icon(Icons.map_rounded), label: 'Route'), + const BottomNavigationBarItem(icon: Icon(Icons.person_rounded), label: 'Profile'), + ]; + + return Scaffold( + appBar: AppBar( + titleSpacing: 16, + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Hi, ${auth.user.fullName.split(' ').first}', + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700)), + Text('${auth.tenantName} · ${auth.user.role == 'helper' ? 'Helper' : 'Scanner'}', + style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF737373))), + ], + ), + actions: [ + IconButton( + tooltip: 'Sign out', + icon: const Icon(Icons.logout_rounded), + onPressed: () => ref.read(authControllerProvider.notifier).logout(), + ), + ], + ), + body: _buildBody(isScanner), + bottomNavigationBar: BottomNavigationBar( + currentIndex: _currentIndex, + onTap: (index) => setState(() => _currentIndex = index), + items: tabs, + selectedItemColor: VerdeColors.verde700, + unselectedItemColor: const Color(0xFF737373), + type: BottomNavigationBarType.fixed, + selectedLabelStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: 12), + unselectedLabelStyle: const TextStyle(fontWeight: FontWeight.w600, fontSize: 11), + ), + ); + } + + Widget _buildBody(bool isScanner) { + if (isScanner) { + if (_currentIndex == 0) return const ScannerScanTab(); + if (_currentIndex == 1) return const ScannerMapTab(); + return const ScannerCrewTab(); + } else { + // Helper + if (_currentIndex == 0) return const HelperAssignmentTab(); + // For helper's profile tab, we can use ScannerCrewTab if they share the same provider, + // but Helper uses HelperAssignmentTab for profile. Wait, ScannerCrewTab uses scannerTeamDetailsProvider which might fail for helper! + // Let's just return a placeholder for Helper Profile for now, or use HelperAssignmentTab for both. + // Wait, HelperAssignmentTab is their assignment and history. We don't have a specific profile tab for Helper. + // I'll just use a simple widget for the Profile tab. + if (_currentIndex == 1) return const Center(child: Text('Profile Information')); + return const SizedBox.shrink(); + } + } +} diff --git a/lib/src/ui/home/home_screen.dart b/lib/src/ui/home/home_screen.dart index fb8c105..1c33a2e 100644 --- a/lib/src/ui/home/home_screen.dart +++ b/lib/src/ui/home/home_screen.dart @@ -9,8 +9,11 @@ import '../../data/scan/scan_queue.dart'; import '../../data/scan/scan_repository.dart'; import '../../theme/app_theme.dart'; -class HomeScreen extends ConsumerWidget { - const HomeScreen({super.key}); +// Legacy HomeScreen logic is moved to CrewHomeScreen. +// The below tabs are exported for CrewHomeScreen to use. + +class ScannerScanTab extends ConsumerWidget { + const ScannerScanTab({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -19,86 +22,9 @@ class HomeScreen extends ConsumerWidget { final auth = ref.watch(authControllerProvider); if (auth is! Authenticated) return const SizedBox.shrink(); - final isScanner = auth.user.role == 'scanner' || auth.user.role == 'admin'; - final recent = ref.watch(recentScansProvider); final acceptedToday = recent.where((r) => r.accepted).length; final pending = ref.watch(pendingCountProvider); - - final tabs = [ - if (isScanner) - const Tab(text: 'Scan', icon: Icon(Icons.qr_code_scanner_rounded, size: 20)), - const Tab(text: 'Crew & Truck', icon: Icon(Icons.local_shipping_rounded, size: 20)), - const Tab(text: 'DOP Map', icon: Icon(Icons.map_rounded, size: 20)), - ]; - - final views = [ - if (isScanner) - _ScanTab( - auth: auth, - recent: recent, - acceptedToday: acceptedToday, - pending: pending, - ), - const _CrewTab(), - const _MapTab(), - ]; - - return DefaultTabController( - length: tabs.length, - child: Scaffold( - appBar: AppBar( - titleSpacing: 16, - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Hi, ${auth.user.fullName.split(' ').first}', - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700)), - Text('${auth.tenantName} · ${auth.user.role == 'helper' ? 'Helper' : 'Scanner'}', - style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF737373))), - ], - ), - actions: [ - IconButton( - tooltip: 'Sign out', - icon: const Icon(Icons.logout_rounded), - onPressed: () => ref.read(authControllerProvider.notifier).logout(), - ), - ], - bottom: TabBar( - tabs: tabs, - labelColor: VerdeColors.verde700, - unselectedLabelColor: const Color(0xFF737373), - indicatorColor: VerdeColors.verde700, - indicatorSize: TabBarIndicatorSize.tab, - labelStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: 13), - ), - ), - body: SafeArea( - child: TabBarView( - children: views, - ), - ), - ), - ); - } -} - -class _ScanTab extends ConsumerWidget { - const _ScanTab({ - required this.auth, - required this.recent, - required this.acceptedToday, - required this.pending, - }); - - final Authenticated auth; - final List recent; - final int acceptedToday; - final int pending; - - @override - Widget build(BuildContext context, WidgetRef ref) { return Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 24), child: Column( @@ -147,8 +73,8 @@ class _ScanTab extends ConsumerWidget { } } -class _CrewTab extends ConsumerWidget { - const _CrewTab(); +class ScannerCrewTab extends ConsumerWidget { + const ScannerCrewTab({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -243,8 +169,8 @@ class _CrewTab extends ConsumerWidget { } } -class _MapTab extends ConsumerWidget { - const _MapTab(); +class ScannerMapTab extends ConsumerWidget { + const ScannerMapTab({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/scanner-crew-home.md b/scanner-crew-home.md new file mode 100644 index 0000000..14b3423 --- /dev/null +++ b/scanner-crew-home.md @@ -0,0 +1,39 @@ +# Scanner Crew Home Refactor + +## Phase -1: Context & Understanding +- **Goal:** Merge the existing `HomeScreen` (Scanner) and `HelperHomeScreen` (Helper) into a single, unified `CrewHomeScreen` with a Bottom Navigation Bar. +- **Roles:** Both Scanner and Helper users will land on this screen. +- **Tabs Available:** + - `Scan`: Only available if `role == 'scanner'`. Shows the DOP picker and camera scan logic. + - `Route`: Available to both. Shows the Active Trip, the Map, and the list of Stops. + - `Profile`: Available to both. Shows Crew and Vehicle details. + +## Phase 0: Socratic Gate (Pre-flight check) +- [x] Does `CrewHomeScreen` need to replace `HomeScreen` entirely? Yes. +- [x] Should we delete `HelperHomeScreen` after merging? Yes, to reduce duplication. +- [x] Are the GoRouter rules properly updated to route both roles to `/home`? Yes, we will need to update `router.dart`. + +## Phase 1: Preparation +1. Review `lib/src/ui/home/home_screen.dart` (current Scanner screen). +2. Review `lib/src/ui/helper/helper_home_screen.dart` (current Helper screen). +3. Review `lib/src/router.dart` for access rules. + +## Phase 2: Implementation - Refactoring the UI +1. **Create `CrewHomeScreen`** (`lib/src/ui/home/crew_home_screen.dart`). +2. Add a `BottomNavigationBar` (or keep `TabBar` if preferred, but BottomNavBar is standard for Flutter apps). +3. **Migrate Views:** + - Migrate `_ScanTab` from `home_screen.dart` to a standalone widget `ScanView`. + - Migrate `HelperHomeScreen` map/stops logic into a standalone widget `RouteView`. + - Migrate `_CrewTab` from `home_screen.dart` to a standalone widget `ProfileView`. +4. Add conditional tab building based on `auth.user.role == 'scanner'`. + +## Phase 3: Implementation - Routing Updates +1. Open `lib/src/router.dart`. +2. Ensure both `scanner` and `helper` roles redirect to `/home`. +3. Point `/home` to `CrewHomeScreen`. +4. Remove dead routes (e.g., `/helper/home`). + +## Phase 4: Verification +- Log in as a Helper: Verify you see Route and Profile tabs. Verify you CANNOT see Scan tab. +- Log in as a Scanner: Verify you see Scan, Route, and Profile tabs. Verify Scan logic still works. +- Check compiler for warnings or missing imports.