Compare commits

..

2 Commits

Author SHA1 Message Date
2905668358 fix 2025-10-28 23:56:47 +07:00
f32e1c16fb fix 2025-10-28 23:46:07 +07:00
14 changed files with 384 additions and 193 deletions

View File

@@ -33,10 +33,10 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e

View File

@@ -72,7 +72,7 @@ class AppRouter {
context.go('/warehouses'); context.go('/warehouses');
}); });
return const _ErrorScreen( return const _ErrorScreen(
message: 'Warehouse data is required', message: 'Yêu cầu dữ liệu kho',
); );
} }
@@ -103,7 +103,7 @@ class AppRouter {
context.go('/warehouses'); context.go('/warehouses');
}); });
return const _ErrorScreen( return const _ErrorScreen(
message: 'Invalid product parameters', message: 'Tham số sản phẩm không hợp lệ',
); );
} }
@@ -143,7 +143,7 @@ class AppRouter {
context.go('/warehouses'); context.go('/warehouses');
}); });
return const _ErrorScreen( return const _ErrorScreen(
message: 'Invalid product detail parameters', message: 'Tham số chi tiết sản phẩm không hợp lệ',
); );
} }
@@ -163,7 +163,7 @@ class AppRouter {
errorBuilder: (context, state) { errorBuilder: (context, state) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Page Not Found'), title: const Text('Không tìm thấy trang'),
), ),
body: Center( body: Center(
child: Column( child: Column(
@@ -176,12 +176,12 @@ class AppRouter {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Page Not Found', 'Không tìm thấy trang',
style: Theme.of(context).textTheme.headlineSmall, style: Theme.of(context).textTheme.headlineSmall,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'The page "${state.uri.path}" does not exist.', 'Trang "${state.uri.path}" không tồn tại.',
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
@@ -190,7 +190,7 @@ class AppRouter {
const SizedBox(height: 24), const SizedBox(height: 24),
ElevatedButton( ElevatedButton(
onPressed: () => context.go('/login'), onPressed: () => context.go('/login'),
child: const Text('Go to Login'), child: const Text('Về trang đăng nhập'),
), ),
], ],
), ),
@@ -283,7 +283,7 @@ class _ErrorScreen extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Error'), title: const Text('Lỗi'),
), ),
body: Center( body: Center(
child: Column( child: Column(
@@ -296,7 +296,7 @@ class _ErrorScreen extends StatelessWidget {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Navigation Error', 'Lỗi điều hướng',
style: Theme.of(context).textTheme.headlineSmall, style: Theme.of(context).textTheme.headlineSmall,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -313,7 +313,7 @@ class _ErrorScreen extends StatelessWidget {
const SizedBox(height: 24), const SizedBox(height: 24),
ElevatedButton( ElevatedButton(
onPressed: () => context.go('/warehouses'), onPressed: () => context.go('/warehouses'),
child: const Text('Go to Warehouses'), child: const Text('Về trang kho'),
), ),
], ],
), ),

View File

@@ -50,15 +50,9 @@ class AuthRepositoryImpl implements AuthRepository {
@override @override
Future<Either<Failure, void>> logout() async { Future<Either<Failure, void>> logout() async {
try { try {
// Call remote data source to logout (optional - can fail silently) // Just clear access token from secure storage
try { // No API call needed
await remoteDataSource.logout(); await secureStorage.clearTokens();
} catch (e) {
// Ignore remote logout errors, still clear local data
}
// Clear all local authentication data
await secureStorage.clearAll();
return const Right(null); return const Right(null);
} catch (e) { } catch (e) {

View File

@@ -78,7 +78,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
// App title // App title
Text( Text(
'Warehouse Manager', 'Quản lý kho',
style: theme.textTheme.headlineMedium?.copyWith( style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: theme.colorScheme.onSurface, color: theme.colorScheme.onSurface,
@@ -90,7 +90,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
// Subtitle // Subtitle
Text( Text(
'Login to continue', 'Đăng nhập để tiếp tục',
style: theme.textTheme.bodyLarge?.copyWith( style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.6), color: theme.colorScheme.onSurface.withOpacity(0.6),
), ),

View File

@@ -129,24 +129,11 @@ class AuthNotifier extends StateNotifier<AuthState> {
/// ///
/// Clears authentication data and returns to initial state /// Clears authentication data and returns to initial state
Future<void> logout() async { Future<void> logout() async {
// Set loading state // Clear tokens from secure storage
state = state.copyWith(isLoading: true, error: null); await logoutUseCase();
// Call logout use case // Always reset to initial state (clear local data even if API call fails)
final result = await logoutUseCase(); state = const AuthState.initial();
// 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();
},
);
} }
/// Check authentication status on app start /// Check authentication status on app start

View File

@@ -56,8 +56,8 @@ class _LoginFormState extends State<LoginForm> {
controller: _usernameController, controller: _usernameController,
enabled: !widget.isLoading, enabled: !widget.isLoading,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Username', labelText: 'Tên đăng nhập',
hintText: 'Enter your username', hintText: 'Nhập tên đăng nhập',
prefixIcon: const Icon(Icons.person_outline), prefixIcon: const Icon(Icons.person_outline),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@@ -67,10 +67,10 @@ class _LoginFormState extends State<LoginForm> {
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
validator: (value) { validator: (value) {
if (value == null || value.trim().isEmpty) { 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) { 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; return null;
}, },
@@ -84,8 +84,8 @@ class _LoginFormState extends State<LoginForm> {
enabled: !widget.isLoading, enabled: !widget.isLoading,
obscureText: _obscurePassword, obscureText: _obscurePassword,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Password', labelText: 'Mật khẩu',
hintText: 'Enter your password', hintText: 'Nhập mật khẩu',
prefixIcon: const Icon(Icons.lock_outline), prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon( icon: Icon(
@@ -107,10 +107,10 @@ class _LoginFormState extends State<LoginForm> {
onFieldSubmitted: (_) => _handleSubmit(), onFieldSubmitted: (_) => _handleSubmit(),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Password is required'; return 'Vui lòng nhập mật khẩu';
} }
if (value.length < 6) { 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; return null;
}, },
@@ -131,7 +131,7 @@ class _LoginFormState extends State<LoginForm> {
), ),
) )
: const Icon(Icons.login), : 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( style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(

View File

@@ -33,7 +33,7 @@ class ProductStageEntity extends Equatable {
/// Get display name for the stage /// Get display name for the stage
/// Returns "No Stage" if stageName is null /// Returns "No Stage" if stageName is null
String get displayName => stageName ?? 'No Stage'; String get displayName => stageName ?? 'Không tên';
/// Check if this is a valid stage (has a stage name) /// Check if this is a valid stage (has a stage name)
bool get hasStage => stageName != null && stageName!.isNotEmpty; bool get hasStage => stageName != null && stageName!.isNotEmpty;

View File

@@ -80,10 +80,10 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
} }
Future<void> _onRefresh() async { Future<void> _onRefresh() async {
await ref.read(productDetailProvider(_providerKey).notifier).refreshProductDetail( // await ref.read(productDetailProvider(_providerKey).notifier).refreshProductDetail(
widget.warehouseId, // widget.warehouseId,
widget.productId, // widget.productId,
); // );
} }
void _clearControllers() { void _clearControllers() {
@@ -114,7 +114,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
final productName = stages.isNotEmpty ? stages.first.productName : 'Product'; final productName = stages.isNotEmpty ? stages.first.productName : 'Product';
// Capitalize first letter of operation type // Capitalize first letter of operation type
final operationTitle = widget.operationType == 'import' ? 'Import' : 'Export'; final operationTitle = widget.operationType == 'import' ? 'Nhập' : 'Xuất';
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@@ -137,7 +137,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
IconButton( IconButton(
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
onPressed: _onRefresh, onPressed: _onRefresh,
tooltip: 'Refresh', tooltip: 'Làm mới',
), ),
], ],
), ),
@@ -195,7 +195,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
FilledButton.icon( FilledButton.icon(
onPressed: _onRefresh, onPressed: _onRefresh,
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
label: const Text('Retry'), label: const Text('Thử lại'),
), ),
], ],
), ),
@@ -215,7 +215,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'No stages found', 'Không tìm thấy công đoạn',
style: theme.textTheme.titleLarge, style: theme.textTheme.titleLarge,
), ),
], ],
@@ -244,14 +244,14 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Stage Not Found', 'Không tìm thấy công đoạn',
style: theme.textTheme.titleLarge?.copyWith( style: theme.textTheme.titleLarge?.copyWith(
color: theme.colorScheme.error, color: theme.colorScheme.error,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Stage with ID ${widget.stageId} was not found in this product.', 'Công đoạn với ID ${widget.stageId} không được tìm thấy trong sản phẩm này.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium, style: theme.textTheme.bodyMedium,
), ),
@@ -259,7 +259,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
FilledButton.icon( FilledButton.icon(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
label: const Text('Go Back'), label: const Text('Quay lại'),
), ),
], ],
), ),
@@ -293,8 +293,8 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
children: [ children: [
Text( Text(
widget.stageId != null widget.stageId != null
? 'Selected Stage' ? 'Công đoạn'
: 'Production Stages (${displayStages.length})', : 'Công đoạn (${displayStages.length})',
style: theme.textTheme.titleSmall?.copyWith( style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -368,7 +368,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
: selectedStage; : selectedStage;
if (stageToShow == null) { 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( return SingleChildScrollView(
@@ -383,32 +383,32 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
_buildSectionCard( _buildSectionCard(
theme: theme, theme: theme,
title: 'Stage Information', title: 'Thông tin công đoạn',
icon: Icons.info_outlined, icon: Icons.info_outlined,
children: [ children: [
_buildInfoRow('Product ID', '${stageToShow.productId}'), _buildInfoRow('Mã sản phẩm', '${stageToShow.productId}'),
if (stageToShow.productStageId != null) if (stageToShow.productStageId != null)
_buildInfoRow('Stage ID', '${stageToShow.productStageId}'), _buildInfoRow('Mã công đoạn', '${stageToShow.productStageId}'),
if (stageToShow.actionTypeId != null) if (stageToShow.actionTypeId != null)
_buildInfoRow('Action Type ID', '${stageToShow.actionTypeId}'), _buildInfoRow('Mã loại thao tác', '${stageToShow.actionTypeId}'),
_buildInfoRow('Stage Name', stageToShow.displayName), _buildInfoRow('Tên công đoạn', stageToShow.displayName),
], ],
), ),
// Current Quantity information // Current Quantity information
_buildSectionCard( _buildSectionCard(
theme: theme, theme: theme,
title: 'Current Quantities', title: 'Số lượng hiện tại',
icon: Icons.info_outlined, icon: Icons.info_outlined,
children: [ children: [
_buildInfoRow('Passed Quantity', '${stageToShow.passedQuantity}'), _buildInfoRow('Số lượng đạt', '${stageToShow.passedQuantity}'),
_buildInfoRow( _buildInfoRow(
'Passed Weight', 'Khối lượng đạt',
'${stageToShow.passedQuantityWeight.toStringAsFixed(2)} kg', '${stageToShow.passedQuantityWeight.toStringAsFixed(2)} kg',
), ),
_buildInfoRow('Issued Quantity', '${stageToShow.issuedQuantity}'), _buildInfoRow('Số lượng lỗi', '${stageToShow.issuedQuantity}'),
_buildInfoRow( _buildInfoRow(
'Issued Weight', 'Khối lượng lỗi',
'${stageToShow.issuedQuantityWeight.toStringAsFixed(2)} kg', '${stageToShow.issuedQuantityWeight.toStringAsFixed(2)} kg',
), ),
], ],
@@ -417,29 +417,29 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
// Add New Quantities section // Add New Quantities section
_buildSectionCard( _buildSectionCard(
theme: theme, theme: theme,
title: 'Add New Quantities', title: 'Thêm số lượng mới',
icon: Icons.add_circle_outline, icon: Icons.add_circle_outline,
children: [ children: [
_buildTextField( _buildTextField(
label: 'Passed Quantity', label: 'Số lượng đạt',
controller: _passedQuantityController, controller: _passedQuantityController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
theme: theme, theme: theme,
), ),
_buildTextField( _buildTextField(
label: 'Passed Weight (kg)', label: 'Khối lượng đạt (kg)',
controller: _passedWeightController, controller: _passedWeightController,
keyboardType: const TextInputType.numberWithOptions(decimal: true), keyboardType: const TextInputType.numberWithOptions(decimal: true),
theme: theme, theme: theme,
), ),
_buildTextField( _buildTextField(
label: 'Issued Quantity', label: 'Số lượng lỗi',
controller: _issuedQuantityController, controller: _issuedQuantityController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
theme: theme, theme: theme,
), ),
_buildTextField( _buildTextField(
label: 'Issued Weight (kg)', label: 'Khối lượng lỗi (kg)',
controller: _issuedWeightController, controller: _issuedWeightController,
keyboardType: const TextInputType.numberWithOptions(decimal: true), keyboardType: const TextInputType.numberWithOptions(decimal: true),
theme: theme, theme: theme,
@@ -450,7 +450,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
_buildSectionCard(theme: theme, title: "Nhân viên", icon: Icons.people, children: [ _buildSectionCard(theme: theme, title: "Nhân viên", icon: Icons.people, children: [
// Warehouse User Dropdown // Warehouse User Dropdown
_buildUserDropdown( _buildUserDropdown(
label: 'Warehouse User', label: 'Người dùng kho',
value: _selectedWarehouseUser, value: _selectedWarehouseUser,
users: ref.watch(usersListProvider) users: ref.watch(usersListProvider)
.where((user) => user.isWareHouseUser) .where((user) => user.isWareHouseUser)
@@ -464,7 +464,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
), ),
// All Employees Dropdown // All Employees Dropdown
_buildUserDropdown( _buildUserDropdown(
label: 'Employee', label: 'Nhân viên',
value: _selectedEmployee, value: _selectedEmployee,
users: ref.watch(usersListProvider), users: ref.watch(usersListProvider),
onChanged: (user) { onChanged: (user) {
@@ -485,7 +485,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
child: OutlinedButton.icon( child: OutlinedButton.icon(
onPressed: () => _printQuantities(stageToShow), onPressed: () => _printQuantities(stageToShow),
icon: const Icon(Icons.print), icon: const Icon(Icons.print),
label: const Text('Print'), label: const Text('In'),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
), ),
@@ -495,7 +495,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
child: FilledButton.icon( child: FilledButton.icon(
onPressed: () => _addNewQuantities(stageToShow), onPressed: () => _addNewQuantities(stageToShow),
icon: const Icon(Icons.save), icon: const Icon(Icons.save),
label: const Text('Save'), label: const Text('Lưu'),
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
), ),
@@ -517,7 +517,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
// TODO: Implement print functionality // TODO: Implement print functionality
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text('Print functionality coming soon'), content: Text('Tính năng in đang phát triển'),
duration: Duration(seconds: 2), duration: Duration(seconds: 2),
), ),
); );
@@ -535,7 +535,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
issuedQuantity == 0 && issuedWeight == 0.0) { issuedQuantity == 0 && issuedWeight == 0.0) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( 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, backgroundColor: Colors.orange,
), ),
); );
@@ -546,7 +546,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
if (_selectedEmployee == null || _selectedWarehouseUser == null) { if (_selectedEmployee == null || _selectedWarehouseUser == null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( 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, backgroundColor: Colors.orange,
), ),
); );
@@ -607,7 +607,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Failed to add quantities: ${failure.message}'), content: Text('Lỗi khi thêm số lượng: ${failure.message}'),
backgroundColor: Colors.red, backgroundColor: Colors.red,
duration: const Duration(seconds: 3), duration: const Duration(seconds: 3),
), ),
@@ -619,7 +619,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text('Quantities added successfully!'), content: Text('Đã thêm số lượng thành công!'),
backgroundColor: Colors.green, backgroundColor: Colors.green,
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
), ),
@@ -644,7 +644,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Error: ${e.toString()}'), content: Text('Lỗi: ${e.toString()}'),
backgroundColor: Colors.red, backgroundColor: Colors.red,
duration: const Duration(seconds: 3), duration: const Duration(seconds: 3),
), ),
@@ -686,7 +686,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'Product ID: ${stage.productId}', 'Sản phẩm ID: ${stage.productId}',
style: theme.textTheme.bodyMedium?.copyWith( style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant, color: theme.colorScheme.onSurfaceVariant,
), ),
@@ -892,7 +892,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
vertical: 12, vertical: 12,
), ),
), ),
hint: Text('Select $label'), hint: Text('Chọn $label'),
items: users.map((user) { items: users.map((user) {
return DropdownMenuItem<UserEntity>( return DropdownMenuItem<UserEntity>(
value: user, value: user,
@@ -921,61 +921,4 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
} }
} }
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,
),
],
),
),
),
),
],
);
}
} }

View File

@@ -121,7 +121,7 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
const SizedBox(width: 12), const SizedBox(width: 12),
const Expanded( const Expanded(
child: Text( child: Text(
'Scan Barcode', 'Quét mã vạch',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 18, fontSize: 18,
@@ -161,7 +161,7 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
color: Colors.grey.shade900, color: Colors.grey.shade900,
child: const Text( 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( style: TextStyle(
color: Colors.white70, color: Colors.white70,
fontSize: 14, fontSize: 14,
@@ -199,7 +199,7 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
// Invalid barcode format // Invalid barcode format
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Invalid barcode format: "$barcode"'), content: Text('Định dạng mã vạch không hợp lệ: "$barcode"'),
backgroundColor: Colors.red, backgroundColor: Colors.red,
action: SnackBarAction( action: SnackBarAction(
label: 'OK', label: 'OK',
@@ -212,11 +212,12 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
} }
// Navigate to product detail with productId and optional stageId // Navigate to product detail with productId and optional stageId
// Use the currently selected tab's operation type
context.goToProductDetail( context.goToProductDetail(
warehouseId: widget.warehouseId, warehouseId: widget.warehouseId,
productId: productId, productId: productId,
warehouseName: widget.warehouseName, warehouseName: widget.warehouseName,
operationType: widget.operationType, operationType: _currentOperationType,
stageId: stageId, stageId: stageId,
); );
} }
@@ -250,7 +251,7 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Products', 'Sản phẩm',
style: textTheme.titleMedium, style: textTheme.titleMedium,
), ),
Text( Text(
@@ -265,7 +266,7 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
IconButton( IconButton(
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
onPressed: _onRefresh, onPressed: _onRefresh,
tooltip: 'Refresh', tooltip: 'Làm mới',
), ),
], ],
bottom: TabBar( bottom: TabBar(
@@ -273,11 +274,11 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
tabs: const [ tabs: const [
Tab( Tab(
icon: Icon(Icons.arrow_downward), icon: Icon(Icons.arrow_downward),
text: 'Import', text: 'Nhập kho',
), ),
Tab( Tab(
icon: Icon(Icons.arrow_upward), icon: Icon(Icons.arrow_upward),
text: 'Export', text: 'Xuất kho',
), ),
], ],
), ),
@@ -291,7 +292,7 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
floatingActionButton: products.isNotEmpty floatingActionButton: products.isNotEmpty
? FloatingActionButton( ? FloatingActionButton(
onPressed: _showBarcodeScanner, onPressed: _showBarcodeScanner,
tooltip: 'Scan Barcode', tooltip: 'Quét mã vạch',
child: const Icon(Icons.qr_code_scanner), child: const Icon(Icons.qr_code_scanner),
) )
: null, : null,
@@ -336,14 +337,14 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
children: [ children: [
Text( Text(
_currentOperationType == 'import' _currentOperationType == 'import'
? 'Import Products' ? 'Nhập kho'
: 'Export Products', : 'Xuất kho',
style: theme.textTheme.titleSmall?.copyWith( style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
Text( Text(
'Warehouse: ${widget.warehouseName}', 'Kho: ${widget.warehouseName}',
style: theme.textTheme.bodySmall?.copyWith( style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant, color: theme.colorScheme.onSurfaceVariant,
), ),
@@ -385,7 +386,7 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
children: [ children: [
CircularProgressIndicator(), CircularProgressIndicator(),
SizedBox(height: 16), SizedBox(height: 16),
Text('Loading products...'), Text('Đang tải sản phẩm...'),
], ],
), ),
); );
@@ -406,7 +407,7 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Error', 'Lỗi',
style: theme.textTheme.titleLarge?.copyWith( style: theme.textTheme.titleLarge?.copyWith(
color: theme.colorScheme.error, color: theme.colorScheme.error,
), ),
@@ -421,7 +422,7 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
FilledButton.icon( FilledButton.icon(
onPressed: _onRefresh, onPressed: _onRefresh,
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
label: const Text('Retry'), label: const Text('Thử lại'),
), ),
], ],
), ),
@@ -444,12 +445,12 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'No Products', 'Không có sản phẩm',
style: theme.textTheme.titleLarge, style: theme.textTheme.titleLarge,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( 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, textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith( style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant, color: theme.colorScheme.onSurfaceVariant,
@@ -459,7 +460,7 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
FilledButton.icon( FilledButton.icon(
onPressed: _onRefresh, onPressed: _onRefresh,
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
label: const Text('Refresh'), label: const Text('Làm mới'),
), ),
], ],
), ),
@@ -478,12 +479,12 @@ class _ProductsPageState extends ConsumerState<ProductsPage>
return ProductListItem( return ProductListItem(
product: product, product: product,
onTap: () { onTap: () {
// Navigate to product detail page // Navigate to product detail page with current tab's operation type
context.goToProductDetail( context.goToProductDetail(
warehouseId: widget.warehouseId, warehouseId: widget.warehouseId,
productId: product.id, productId: product.id,
warehouseName: widget.warehouseName, warehouseName: widget.warehouseName,
operationType: widget.operationType, operationType: _currentOperationType,
); );
}, },
); );

View File

@@ -45,7 +45,7 @@ class ProductListItem extends StatelessWidget {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'Code: ${product.code}', ': ${product.code}',
style: textTheme.bodySmall?.copyWith( style: textTheme.bodySmall?.copyWith(
color: theme.colorScheme.primary, color: theme.colorScheme.primary,
), ),
@@ -65,7 +65,7 @@ class ProductListItem extends StatelessWidget {
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
child: Text( child: Text(
'Active', 'Hoạt động',
style: textTheme.labelSmall?.copyWith( style: textTheme.labelSmall?.copyWith(
color: Colors.green, color: Colors.green,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -84,7 +84,7 @@ class ProductListItem extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: _InfoItem( child: _InfoItem(
label: 'Weight', label: 'Khối lượng',
value: '${product.weight.toStringAsFixed(2)} kg', value: '${product.weight.toStringAsFixed(2)} kg',
icon: Icons.fitness_center, icon: Icons.fitness_center,
), ),
@@ -92,7 +92,7 @@ class ProductListItem extends StatelessWidget {
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: _InfoItem( child: _InfoItem(
label: 'Pieces', label: 'Số lượng',
value: product.pieces.toString(), value: product.pieces.toString(),
icon: Icons.inventory_2, icon: Icons.inventory_2,
), ),
@@ -107,7 +107,7 @@ class ProductListItem extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: _InfoItem( child: _InfoItem(
label: 'In Stock (Pieces)', label: 'Tồn kho (SL)',
value: product.piecesInStock.toString(), value: product.piecesInStock.toString(),
icon: Icons.warehouse, icon: Icons.warehouse,
color: product.piecesInStock > 0 color: product.piecesInStock > 0
@@ -118,7 +118,7 @@ class ProductListItem extends StatelessWidget {
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: _InfoItem( child: _InfoItem(
label: 'In Stock (Weight)', label: 'Tồn kho (KL)',
value: '${product.weightInStock.toStringAsFixed(2)} kg', value: '${product.weightInStock.toStringAsFixed(2)} kg',
icon: Icons.scale, icon: Icons.scale,
color: product.weightInStock > 0 color: product.weightInStock > 0
@@ -142,7 +142,7 @@ class ProductListItem extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'Conversion Rate', 'Tỷ lệ chuyển đổi',
style: textTheme.bodyMedium?.copyWith( style: textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
@@ -170,7 +170,7 @@ class ProductListItem extends StatelessWidget {
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'Barcode: ${product.barcode}', 'Mã vạch: ${product.barcode}',
style: textTheme.bodySmall?.copyWith( style: textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant, color: theme.colorScheme.onSurfaceVariant,
), ),

View File

@@ -12,7 +12,10 @@ class UsersNotifier extends StateNotifier<UsersState> {
UsersNotifier({ UsersNotifier({
required this.getUsersUseCase, required this.getUsersUseCase,
required this.syncUsersUseCase, 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) /// Get users from local storage (or API if not cached)
Future<void> getUsers() async { Future<void> getUsers() async {

View File

@@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/di/providers.dart'; import '../../../../core/di/providers.dart';
import '../../../../core/router/app_router.dart'; import '../../../../core/router/app_router.dart';
import '../widgets/warehouse_card.dart'; import '../widgets/warehouse_card.dart';
import '../widgets/warehouse_drawer.dart';
/// Warehouse selection page /// Warehouse selection page
/// Displays a list of warehouses and allows user to select one /// Displays a list of warehouses and allows user to select one
@@ -26,11 +27,10 @@ class _WarehouseSelectionPageState
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Load warehouses and sync users when page is first created // Load warehouses when page is first created
Future.microtask(() { Future.microtask(() {
ref.read(warehouseProvider.notifier).loadWarehouses(); ref.read(warehouseProvider.notifier).loadWarehouses();
// Sync users from API and save to local storage // Users are automatically loaded from local storage by UsersNotifier
ref.read(usersProvider.notifier).syncUsers();
}); });
} }
@@ -44,17 +44,18 @@ class _WarehouseSelectionPageState
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Select Warehouse'), title: const Text('Chọn kho'),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
onPressed: () { onPressed: () {
ref.read(warehouseProvider.notifier).loadWarehouses(); ref.read(warehouseProvider.notifier).loadWarehouses();
}, },
tooltip: 'Refresh', tooltip: 'Làm mới',
), ),
], ],
), ),
drawer: const WarehouseDrawer(),
body: _buildBody( body: _buildBody(
isLoading: isLoading, isLoading: isLoading,
error: error, error: error,
@@ -77,7 +78,7 @@ class _WarehouseSelectionPageState
children: [ children: [
CircularProgressIndicator(), CircularProgressIndicator(),
SizedBox(height: 16), SizedBox(height: 16),
Text('Loading warehouses...'), Text('Đang tải danh sách kho...'),
], ],
), ),
); );
@@ -98,7 +99,7 @@ class _WarehouseSelectionPageState
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Error Loading Warehouses', 'Lỗi tải danh sách kho',
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -113,7 +114,7 @@ class _WarehouseSelectionPageState
ref.read(warehouseProvider.notifier).loadWarehouses(); ref.read(warehouseProvider.notifier).loadWarehouses();
}, },
icon: const Icon(Icons.refresh), 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), const SizedBox(height: 16),
Text( Text(
'No Warehouses Available', 'Không có kho',
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'There are no warehouses to display.', 'Không có kho nào để hiển thị.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.bodyMedium,
), ),
@@ -151,7 +152,7 @@ class _WarehouseSelectionPageState
ref.read(warehouseProvider.notifier).loadWarehouses(); ref.read(warehouseProvider.notifier).loadWarehouses();
}, },
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
label: const Text('Refresh'), label: const Text('Làm mới'),
), ),
], ],
), ),

View File

@@ -49,7 +49,7 @@ class WarehouseCard extends StatelessWidget {
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'Code: ${warehouse.code}', ': ${warehouse.code}',
style: theme.textTheme.bodyMedium?.copyWith( style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant, color: colorScheme.onSurfaceVariant,
), ),
@@ -68,7 +68,7 @@ class WarehouseCard extends StatelessWidget {
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'Items: ${warehouse.totalCount}', 'Sản phẩm: ${warehouse.totalCount}',
style: theme.textTheme.bodyMedium?.copyWith( style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant, color: colorScheme.onSurfaceVariant,
), ),
@@ -104,7 +104,7 @@ class WarehouseCard extends StatelessWidget {
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
child: Text( child: Text(
'NG Warehouse', 'Kho NG',
style: theme.textTheme.labelSmall?.copyWith( style: theme.textTheme.labelSmall?.copyWith(
color: colorScheme.onErrorContainer, color: colorScheme.onErrorContainer,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

View File

@@ -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<Color>(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<bool?> _showLogoutDialog(BuildContext context) {
return showDialog<bool>(
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'),
),
],
),
);
}
}