diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2500113..f8dee5d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -33,10 +33,10 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 - mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d + flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e diff --git a/lib/features/auth/data/repositories/auth_repository_impl.dart b/lib/features/auth/data/repositories/auth_repository_impl.dart index 52927b8..436c111 100644 --- a/lib/features/auth/data/repositories/auth_repository_impl.dart +++ b/lib/features/auth/data/repositories/auth_repository_impl.dart @@ -50,15 +50,9 @@ class AuthRepositoryImpl implements AuthRepository { @override Future> logout() async { try { - // Call remote data source to logout (optional - can fail silently) - try { - await remoteDataSource.logout(); - } catch (e) { - // Ignore remote logout errors, still clear local data - } - - // Clear all local authentication data - await secureStorage.clearAll(); + // Just clear access token from secure storage + // No API call needed + await secureStorage.clearTokens(); return const Right(null); } catch (e) { diff --git a/lib/features/auth/presentation/pages/login_page.dart b/lib/features/auth/presentation/pages/login_page.dart index 7222192..024d3a8 100644 --- a/lib/features/auth/presentation/pages/login_page.dart +++ b/lib/features/auth/presentation/pages/login_page.dart @@ -78,7 +78,7 @@ class _LoginPageState extends ConsumerState { // App title Text( - 'Warehouse Manager', + 'Quản lý kho', style: theme.textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.onSurface, @@ -90,7 +90,7 @@ class _LoginPageState extends ConsumerState { // Subtitle Text( - 'Login to continue', + 'Đăng nhập để tiếp tục', style: theme.textTheme.bodyLarge?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.6), ), diff --git a/lib/features/auth/presentation/providers/auth_provider.dart b/lib/features/auth/presentation/providers/auth_provider.dart index 7eebae1..26d6d71 100644 --- a/lib/features/auth/presentation/providers/auth_provider.dart +++ b/lib/features/auth/presentation/providers/auth_provider.dart @@ -129,24 +129,11 @@ class AuthNotifier extends StateNotifier { /// /// Clears authentication data and returns to initial state Future logout() async { - // Set loading state - state = state.copyWith(isLoading: true, error: null); + // Clear tokens from secure storage + await logoutUseCase(); - // Call logout use case - final result = await logoutUseCase(); - - // Handle result - result.fold( - (failure) { - // Logout failed - but still reset to initial state - // (local data should be cleared even if API call fails) - state = const AuthState.initial(); - }, - (_) { - // Logout successful - reset to initial state - state = const AuthState.initial(); - }, - ); + // Always reset to initial state (clear local data even if API call fails) + state = const AuthState.initial(); } /// Check authentication status on app start diff --git a/lib/features/auth/presentation/widgets/login_form.dart b/lib/features/auth/presentation/widgets/login_form.dart index 45be4b4..fac2abe 100644 --- a/lib/features/auth/presentation/widgets/login_form.dart +++ b/lib/features/auth/presentation/widgets/login_form.dart @@ -56,8 +56,8 @@ class _LoginFormState extends State { controller: _usernameController, enabled: !widget.isLoading, decoration: InputDecoration( - labelText: 'Username', - hintText: 'Enter your username', + labelText: 'Tên đăng nhập', + hintText: 'Nhập tên đăng nhập', prefixIcon: const Icon(Icons.person_outline), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), @@ -67,10 +67,10 @@ class _LoginFormState extends State { textInputAction: TextInputAction.next, validator: (value) { if (value == null || value.trim().isEmpty) { - return 'Username is required'; + return 'Vui lòng nhập tên đăng nhập'; } if (value.trim().length < 3) { - return 'Username must be at least 3 characters'; + return 'Tên đăng nhập phải có ít nhất 3 ký tự'; } return null; }, @@ -84,8 +84,8 @@ class _LoginFormState extends State { enabled: !widget.isLoading, obscureText: _obscurePassword, decoration: InputDecoration( - labelText: 'Password', - hintText: 'Enter your password', + labelText: 'Mật khẩu', + hintText: 'Nhập mật khẩu', prefixIcon: const Icon(Icons.lock_outline), suffixIcon: IconButton( icon: Icon( @@ -107,10 +107,10 @@ class _LoginFormState extends State { onFieldSubmitted: (_) => _handleSubmit(), validator: (value) { if (value == null || value.isEmpty) { - return 'Password is required'; + return 'Vui lòng nhập mật khẩu'; } if (value.length < 6) { - return 'Password must be at least 6 characters'; + return 'Mật khẩu phải có ít nhất 6 ký tự'; } return null; }, @@ -131,7 +131,7 @@ class _LoginFormState extends State { ), ) : const Icon(Icons.login), - label: Text(widget.isLoading ? 'Logging in...' : 'Login'), + label: Text(widget.isLoading ? 'Đang đăng nhập...' : 'Đăng nhập'), style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( diff --git a/lib/features/products/presentation/pages/product_detail_page.dart b/lib/features/products/presentation/pages/product_detail_page.dart index 26cd00f..cc6ab0b 100644 --- a/lib/features/products/presentation/pages/product_detail_page.dart +++ b/lib/features/products/presentation/pages/product_detail_page.dart @@ -195,7 +195,7 @@ class _ProductDetailPageState extends ConsumerState { FilledButton.icon( onPressed: _onRefresh, icon: const Icon(Icons.refresh), - label: const Text('Retry'), + label: const Text('Thử lại'), ), ], ), @@ -259,7 +259,7 @@ class _ProductDetailPageState extends ConsumerState { FilledButton.icon( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.arrow_back), - label: const Text('Go Back'), + label: const Text('Quay lại'), ), ], ), @@ -368,7 +368,7 @@ class _ProductDetailPageState extends ConsumerState { : selectedStage; if (stageToShow == null) { - return const Center(child: Text('No stage selected')); + return const Center(child: Text('Chưa chọn công đoạn')); } return SingleChildScrollView( @@ -485,7 +485,7 @@ class _ProductDetailPageState extends ConsumerState { child: OutlinedButton.icon( onPressed: () => _printQuantities(stageToShow), icon: const Icon(Icons.print), - label: const Text('Print'), + label: const Text('In'), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), ), @@ -495,7 +495,7 @@ class _ProductDetailPageState extends ConsumerState { child: FilledButton.icon( onPressed: () => _addNewQuantities(stageToShow), icon: const Icon(Icons.save), - label: const Text('Save'), + label: const Text('Lưu'), style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), ), @@ -517,7 +517,7 @@ class _ProductDetailPageState extends ConsumerState { // TODO: Implement print functionality ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('Print functionality coming soon'), + content: Text('Tính năng in đang phát triển'), duration: Duration(seconds: 2), ), ); @@ -535,7 +535,7 @@ class _ProductDetailPageState extends ConsumerState { issuedQuantity == 0 && issuedWeight == 0.0) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('Please enter at least one quantity or weight value'), + content: Text('Vui lòng nhập ít nhất một giá trị số lượng hoặc khối lượng'), backgroundColor: Colors.orange, ), ); @@ -546,7 +546,7 @@ class _ProductDetailPageState extends ConsumerState { if (_selectedEmployee == null || _selectedWarehouseUser == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('Please select both Employee and Warehouse User'), + content: Text('Vui lòng chọn cả Nhân viên và Người dùng kho'), backgroundColor: Colors.orange, ), ); @@ -607,7 +607,7 @@ class _ProductDetailPageState extends ConsumerState { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Failed to add quantities: ${failure.message}'), + content: Text('Lỗi khi thêm số lượng: ${failure.message}'), backgroundColor: Colors.red, duration: const Duration(seconds: 3), ), @@ -619,7 +619,7 @@ class _ProductDetailPageState extends ConsumerState { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('Quantities added successfully!'), + content: Text('Đã thêm số lượng thành công!'), backgroundColor: Colors.green, duration: const Duration(seconds: 2), ), @@ -644,7 +644,7 @@ class _ProductDetailPageState extends ConsumerState { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Error: ${e.toString()}'), + content: Text('Lỗi: ${e.toString()}'), backgroundColor: Colors.red, duration: const Duration(seconds: 3), ), @@ -892,7 +892,7 @@ class _ProductDetailPageState extends ConsumerState { vertical: 12, ), ), - hint: Text('Select $label'), + hint: Text('Chọn $label'), items: users.map((user) { return DropdownMenuItem( value: user, @@ -921,61 +921,4 @@ class _ProductDetailPageState extends ConsumerState { } } - Widget _buildStatusCards(ProductStageEntity stage, ThemeData theme) { - return Row( - children: [ - Expanded( - child: Card( - color: stage.hasPassedQuantity - ? Colors.green.withValues(alpha: 0.1) - : Colors.grey.withValues(alpha: 0.1), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - Icon( - stage.hasPassedQuantity ? Icons.check_circle : Icons.cancel, - color: stage.hasPassedQuantity ? Colors.green : Colors.grey, - size: 32, - ), - const SizedBox(height: 8), - Text( - 'Has Passed', - style: theme.textTheme.bodySmall, - textAlign: TextAlign.center, - ), - ], - ), - ), - ), - ), - const SizedBox(width: 8), - Expanded( - child: Card( - color: stage.hasIssuedQuantity - ? Colors.blue.withValues(alpha: 0.1) - : Colors.grey.withValues(alpha: 0.1), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - Icon( - stage.hasIssuedQuantity ? Icons.check_circle : Icons.cancel, - color: stage.hasIssuedQuantity ? Colors.blue : Colors.grey, - size: 32, - ), - const SizedBox(height: 8), - Text( - 'Has Issued', - style: theme.textTheme.bodySmall, - textAlign: TextAlign.center, - ), - ], - ), - ), - ), - ), - ], - ); - } } diff --git a/lib/features/products/presentation/pages/products_page.dart b/lib/features/products/presentation/pages/products_page.dart index 872a4a5..f5afc7f 100644 --- a/lib/features/products/presentation/pages/products_page.dart +++ b/lib/features/products/presentation/pages/products_page.dart @@ -121,7 +121,7 @@ class _ProductsPageState extends ConsumerState const SizedBox(width: 12), const Expanded( child: Text( - 'Scan Barcode', + 'Quét mã vạch', style: TextStyle( color: Colors.white, fontSize: 18, @@ -161,7 +161,7 @@ class _ProductsPageState extends ConsumerState padding: const EdgeInsets.all(16), color: Colors.grey.shade900, child: const Text( - 'Position the Code 128 barcode within the frame to scan', + 'Đặt mã vạch Code 128 vào khung để quét', style: TextStyle( color: Colors.white70, fontSize: 14, @@ -199,7 +199,7 @@ class _ProductsPageState extends ConsumerState // Invalid barcode format ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Invalid barcode format: "$barcode"'), + content: Text('Định dạng mã vạch không hợp lệ: "$barcode"'), backgroundColor: Colors.red, action: SnackBarAction( label: 'OK', @@ -212,11 +212,12 @@ class _ProductsPageState extends ConsumerState } // Navigate to product detail with productId and optional stageId + // Use the currently selected tab's operation type context.goToProductDetail( warehouseId: widget.warehouseId, productId: productId, warehouseName: widget.warehouseName, - operationType: widget.operationType, + operationType: _currentOperationType, stageId: stageId, ); } @@ -250,7 +251,7 @@ class _ProductsPageState extends ConsumerState crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Products', + 'Sản phẩm', style: textTheme.titleMedium, ), Text( @@ -265,7 +266,7 @@ class _ProductsPageState extends ConsumerState IconButton( icon: const Icon(Icons.refresh), onPressed: _onRefresh, - tooltip: 'Refresh', + tooltip: 'Làm mới', ), ], bottom: TabBar( @@ -273,11 +274,11 @@ class _ProductsPageState extends ConsumerState tabs: const [ Tab( icon: Icon(Icons.arrow_downward), - text: 'Import', + text: 'Nhập kho', ), Tab( icon: Icon(Icons.arrow_upward), - text: 'Export', + text: 'Xuất kho', ), ], ), @@ -291,7 +292,7 @@ class _ProductsPageState extends ConsumerState floatingActionButton: products.isNotEmpty ? FloatingActionButton( onPressed: _showBarcodeScanner, - tooltip: 'Scan Barcode', + tooltip: 'Quét mã vạch', child: const Icon(Icons.qr_code_scanner), ) : null, @@ -336,14 +337,14 @@ class _ProductsPageState extends ConsumerState children: [ Text( _currentOperationType == 'import' - ? 'Import Products' - : 'Export Products', + ? 'Nhập kho' + : 'Xuất kho', style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, ), ), Text( - 'Warehouse: ${widget.warehouseName}', + 'Kho: ${widget.warehouseName}', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), @@ -385,7 +386,7 @@ class _ProductsPageState extends ConsumerState children: [ CircularProgressIndicator(), SizedBox(height: 16), - Text('Loading products...'), + Text('Đang tải sản phẩm...'), ], ), ); @@ -406,7 +407,7 @@ class _ProductsPageState extends ConsumerState ), const SizedBox(height: 16), Text( - 'Error', + 'Lỗi', style: theme.textTheme.titleLarge?.copyWith( color: theme.colorScheme.error, ), @@ -421,7 +422,7 @@ class _ProductsPageState extends ConsumerState FilledButton.icon( onPressed: _onRefresh, icon: const Icon(Icons.refresh), - label: const Text('Retry'), + label: const Text('Thử lại'), ), ], ), @@ -444,12 +445,12 @@ class _ProductsPageState extends ConsumerState ), const SizedBox(height: 16), Text( - 'No Products', + 'Không có sản phẩm', style: theme.textTheme.titleLarge, ), const SizedBox(height: 8), Text( - 'No products found for this warehouse and operation type.', + 'Không tìm thấy sản phẩm nào cho kho và loại thao tác này.', textAlign: TextAlign.center, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, @@ -459,7 +460,7 @@ class _ProductsPageState extends ConsumerState FilledButton.icon( onPressed: _onRefresh, icon: const Icon(Icons.refresh), - label: const Text('Refresh'), + label: const Text('Làm mới'), ), ], ), @@ -478,12 +479,12 @@ class _ProductsPageState extends ConsumerState return ProductListItem( product: product, onTap: () { - // Navigate to product detail page + // Navigate to product detail page with current tab's operation type context.goToProductDetail( warehouseId: widget.warehouseId, productId: product.id, warehouseName: widget.warehouseName, - operationType: widget.operationType, + operationType: _currentOperationType, ); }, ); diff --git a/lib/features/users/presentation/providers/users_provider.dart b/lib/features/users/presentation/providers/users_provider.dart index 5ce2e1e..c8c459c 100644 --- a/lib/features/users/presentation/providers/users_provider.dart +++ b/lib/features/users/presentation/providers/users_provider.dart @@ -12,7 +12,10 @@ class UsersNotifier extends StateNotifier { UsersNotifier({ required this.getUsersUseCase, required this.syncUsersUseCase, - }) : super(const UsersState()); + }) : super(const UsersState()) { + // Load local users on initialization + getUsers(); + } /// Get users from local storage (or API if not cached) Future getUsers() async { diff --git a/lib/features/warehouse/presentation/pages/warehouse_selection_page.dart b/lib/features/warehouse/presentation/pages/warehouse_selection_page.dart index 85a4054..0d46123 100644 --- a/lib/features/warehouse/presentation/pages/warehouse_selection_page.dart +++ b/lib/features/warehouse/presentation/pages/warehouse_selection_page.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../core/di/providers.dart'; import '../../../../core/router/app_router.dart'; import '../widgets/warehouse_card.dart'; +import '../widgets/warehouse_drawer.dart'; /// Warehouse selection page /// Displays a list of warehouses and allows user to select one @@ -26,11 +27,10 @@ class _WarehouseSelectionPageState @override void initState() { super.initState(); - // Load warehouses and sync users when page is first created + // Load warehouses when page is first created Future.microtask(() { ref.read(warehouseProvider.notifier).loadWarehouses(); - // Sync users from API and save to local storage - ref.read(usersProvider.notifier).syncUsers(); + // Users are automatically loaded from local storage by UsersNotifier }); } @@ -44,17 +44,18 @@ class _WarehouseSelectionPageState return Scaffold( appBar: AppBar( - title: const Text('Select Warehouse'), + title: const Text('Chọn kho'), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: () { ref.read(warehouseProvider.notifier).loadWarehouses(); }, - tooltip: 'Refresh', + tooltip: 'Làm mới', ), ], ), + drawer: const WarehouseDrawer(), body: _buildBody( isLoading: isLoading, error: error, @@ -77,7 +78,7 @@ class _WarehouseSelectionPageState children: [ CircularProgressIndicator(), SizedBox(height: 16), - Text('Loading warehouses...'), + Text('Đang tải danh sách kho...'), ], ), ); @@ -98,7 +99,7 @@ class _WarehouseSelectionPageState ), const SizedBox(height: 16), Text( - 'Error Loading Warehouses', + 'Lỗi tải danh sách kho', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 8), @@ -113,7 +114,7 @@ class _WarehouseSelectionPageState ref.read(warehouseProvider.notifier).loadWarehouses(); }, icon: const Icon(Icons.refresh), - label: const Text('Retry'), + label: const Text('Thử lại'), ), ], ), @@ -136,12 +137,12 @@ class _WarehouseSelectionPageState ), const SizedBox(height: 16), Text( - 'No Warehouses Available', + 'Không có kho', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 8), Text( - 'There are no warehouses to display.', + 'Không có kho nào để hiển thị.', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium, ), @@ -151,7 +152,7 @@ class _WarehouseSelectionPageState ref.read(warehouseProvider.notifier).loadWarehouses(); }, icon: const Icon(Icons.refresh), - label: const Text('Refresh'), + label: const Text('Làm mới'), ), ], ), diff --git a/lib/features/warehouse/presentation/widgets/warehouse_drawer.dart b/lib/features/warehouse/presentation/widgets/warehouse_drawer.dart new file mode 100644 index 0000000..291eeeb --- /dev/null +++ b/lib/features/warehouse/presentation/widgets/warehouse_drawer.dart @@ -0,0 +1,262 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../../core/di/providers.dart'; + +/// Drawer for warehouse selection page +/// Contains app settings and sync options +class WarehouseDrawer extends ConsumerWidget { + const WarehouseDrawer({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + final authState = ref.watch(authProvider); + final usersState = ref.watch(usersProvider); + final user = authState.user; + + return Drawer( + child: SafeArea( + child: Column( + children: [ + // Header with user info + Container( + width: double.infinity, + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: theme.colorScheme.primaryContainer, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CircleAvatar( + radius: 32, + backgroundColor: theme.colorScheme.primary, + child: Icon( + Icons.person, + size: 32, + color: theme.colorScheme.onPrimary, + ), + ), + const SizedBox(height: 16), + Text( + user?.username ?? 'User', + style: theme.textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + color: theme.colorScheme.onPrimaryContainer, + ), + ), + const SizedBox(height: 4), + Text( + 'Quản lý kho', + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onPrimaryContainer.withValues(alpha: 0.8), + ), + ), + ], + ), + ), + + // Menu items + Expanded( + child: ListView( + padding: const EdgeInsets.symmetric(vertical: 8), + children: [ + // Sync Users button + ListTile( + leading: Icon( + Icons.sync, + color: theme.colorScheme.primary, + ), + title: const Text('Đồng bộ người dùng'), + subtitle: Text( + usersState.users.isEmpty + ? 'Chưa có dữ liệu' + : '${usersState.users.length} người dùng', + style: theme.textTheme.bodySmall, + ), + trailing: usersState.isLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : Icon( + Icons.cloud_download, + color: theme.colorScheme.secondary, + ), + onTap: usersState.isLoading + ? null + : () async { + // Close drawer first + Navigator.of(context).pop(); + + // Show loading indicator + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Row( + children: [ + SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ), + SizedBox(width: 16), + Text('Đang đồng bộ...'), + ], + ), + duration: Duration(seconds: 2), + ), + ); + + // Sync users from API + await ref.read(usersProvider.notifier).syncUsers(); + + // Show success or error message + if (context.mounted) { + final error = ref.read(usersProvider).error; + if (error != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + const Icon(Icons.error, color: Colors.white), + const SizedBox(width: 16), + Expanded(child: Text('Lỗi: $error')), + ], + ), + backgroundColor: Colors.red, + action: SnackBarAction( + label: 'OK', + textColor: Colors.white, + onPressed: () {}, + ), + ), + ); + } else { + final count = ref.read(usersProvider).users.length; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + const Icon(Icons.check_circle, color: Colors.white), + const SizedBox(width: 16), + Text('Đã đồng bộ $count người dùng'), + ], + ), + backgroundColor: Colors.green, + duration: const Duration(seconds: 2), + ), + ); + } + } + }, + ), + + const Divider(), + + // Settings (placeholder) + ListTile( + leading: const Icon(Icons.settings), + title: const Text('Cài đặt'), + subtitle: const Text('Tùy chỉnh ứng dụng'), + onTap: () { + Navigator.of(context).pop(); + // TODO: Navigate to settings page + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Tính năng đang phát triển'), + ), + ); + }, + ), + + // About (placeholder) + ListTile( + leading: const Icon(Icons.info_outline), + title: const Text('Thông tin'), + subtitle: const Text('Về ứng dụng'), + onTap: () { + Navigator.of(context).pop(); + showAboutDialog( + context: context, + applicationName: 'Quản lý kho', + applicationVersion: '1.0.0', + applicationIcon: const Icon(Icons.warehouse, size: 48), + children: [ + const Text('Hệ thống quản lý kho và theo dõi sản phẩm.'), + ], + ); + }, + ), + ], + ), + ), + + // Logout button at bottom + const Divider(height: 1), + ListTile( + leading: Icon( + Icons.logout, + color: theme.colorScheme.error, + ), + title: Text( + 'Đăng xuất', + style: TextStyle( + color: theme.colorScheme.error, + fontWeight: FontWeight.bold, + ), + ), + onTap: () async { + // Capture references BEFORE closing drawer (drawer will be disposed) + final authNotifier = ref.read(authProvider.notifier); + final navigator = Navigator.of(context); + final router = GoRouter.of(context); + + navigator.pop(); // Close drawer + + // Show logout confirmation dialog and get result + final shouldLogout = await _showLogoutDialog(context); + + // If user confirmed, logout and navigate to login + if (shouldLogout == true) { + await authNotifier.logout(); + + // Navigate to login screen using captured router + router.go('/login'); + } + }, + ), + ], + ), + ), + ); + } + + Future _showLogoutDialog(BuildContext context) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Đăng xuất'), + content: const Text('Bạn có chắc chắn muốn đăng xuất?'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('Hủy'), + ), + FilledButton( + onPressed: () => Navigator.of(context).pop(true), + style: FilledButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.error, + ), + child: const Text('Đăng xuất'), + ), + ], + ), + ); + } +}