aaa
This commit is contained in:
@@ -1,398 +0,0 @@
|
||||
# Warehouse Feature - Architecture Diagram
|
||||
|
||||
## Clean Architecture Layers
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ PRESENTATION LAYER │
|
||||
│ (UI, State Management, User Interactions) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────┐ ┌──────────────────────────────────┐ │
|
||||
│ │ WarehouseCard │ │ WarehouseSelectionPage │ │
|
||||
│ │ - Shows warehouse │ │ - Displays warehouse list │ │
|
||||
│ │ information │ │ - Handles user selection │ │
|
||||
│ └─────────────────────┘ │ - Pull to refresh │ │
|
||||
│ │ - Loading/Error/Empty states │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ WarehouseNotifier │ │
|
||||
│ │ (StateNotifier) │ │
|
||||
│ │ - loadWarehouses() │ │
|
||||
│ │ - selectWarehouse() │ │
|
||||
│ │ - refresh() │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ WarehouseState │ │
|
||||
│ │ - warehouses: List │ │
|
||||
│ │ - selectedWarehouse: Warehouse? │ │
|
||||
│ │ - isLoading: bool │ │
|
||||
│ │ - error: String? │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ uses
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DOMAIN LAYER │
|
||||
│ (Business Logic, Entities, Use Cases) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ GetWarehousesUseCase │ │
|
||||
│ │ - Encapsulates business logic for fetching warehouses │ │
|
||||
│ │ - Single responsibility │ │
|
||||
│ │ - Returns Either<Failure, List<WarehouseEntity>> │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ uses │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ WarehouseRepository (Interface) │ │
|
||||
│ │ + getWarehouses(): Either<Failure, List<Warehouse>> │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ WarehouseEntity │ │
|
||||
│ │ - id: int │ │
|
||||
│ │ - name: String │ │
|
||||
│ │ - code: String │ │
|
||||
│ │ - description: String? │ │
|
||||
│ │ - isNGWareHouse: bool │ │
|
||||
│ │ - totalCount: int │ │
|
||||
│ │ + hasItems: bool │ │
|
||||
│ │ + isNGType: bool │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ implements
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DATA LAYER │
|
||||
│ (API Calls, Data Sources, Models) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ WarehouseRepositoryImpl │ │
|
||||
│ │ - Implements WarehouseRepository interface │ │
|
||||
│ │ - Coordinates data sources │ │
|
||||
│ │ - Converts exceptions to failures │ │
|
||||
│ │ - Maps models to entities │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ uses │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ WarehouseRemoteDataSource (Interface) │ │
|
||||
│ │ + getWarehouses(): Future<List<WarehouseModel>> │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ WarehouseRemoteDataSourceImpl │ │
|
||||
│ │ - Makes API calls using ApiClient │ │
|
||||
│ │ - Parses ApiResponse wrapper │ │
|
||||
│ │ - Throws ServerException or NetworkException │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ uses │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ WarehouseModel │ │
|
||||
│ │ - Extends WarehouseEntity │ │
|
||||
│ │ - Adds JSON serialization (fromJson, toJson) │ │
|
||||
│ │ - Maps API fields to entity fields │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ↓ uses │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ ApiClient (Core) │ │
|
||||
│ │ - Dio HTTP client wrapper │ │
|
||||
│ │ - Adds authentication headers │ │
|
||||
│ │ - Handles 401 errors │ │
|
||||
│ │ - Logging and error handling │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
### 1. Loading Warehouses Flow
|
||||
|
||||
```
|
||||
User Action (Pull to Refresh / Page Load)
|
||||
↓
|
||||
WarehouseSelectionPage
|
||||
↓ calls
|
||||
ref.read(warehouseProvider.notifier).loadWarehouses()
|
||||
↓
|
||||
WarehouseNotifier.loadWarehouses()
|
||||
↓ sets state
|
||||
state = state.setLoading() → UI shows loading indicator
|
||||
↓ calls
|
||||
GetWarehousesUseCase.call()
|
||||
↓ calls
|
||||
WarehouseRepository.getWarehouses()
|
||||
↓ calls
|
||||
WarehouseRemoteDataSource.getWarehouses()
|
||||
↓ makes HTTP request
|
||||
ApiClient.get('/warehouses')
|
||||
↓ API Response
|
||||
{
|
||||
"Value": [...],
|
||||
"IsSuccess": true,
|
||||
"IsFailure": false,
|
||||
"Errors": [],
|
||||
"ErrorCodes": []
|
||||
}
|
||||
↓ parse
|
||||
List<WarehouseModel> from JSON
|
||||
↓ convert
|
||||
List<WarehouseEntity>
|
||||
↓ wrap
|
||||
Right(warehouses) or Left(failure)
|
||||
↓ update state
|
||||
state = state.setSuccess(warehouses)
|
||||
↓
|
||||
UI rebuilds with warehouse list
|
||||
```
|
||||
|
||||
### 2. Error Handling Flow
|
||||
|
||||
```
|
||||
API Error / Network Error
|
||||
↓
|
||||
ApiClient throws DioException
|
||||
↓
|
||||
_handleDioError() converts to custom exception
|
||||
↓
|
||||
ServerException or NetworkException
|
||||
↓
|
||||
WarehouseRemoteDataSource catches and rethrows
|
||||
↓
|
||||
WarehouseRepositoryImpl catches exception
|
||||
↓
|
||||
Converts to Failure:
|
||||
- ServerException → ServerFailure
|
||||
- NetworkException → NetworkFailure
|
||||
↓
|
||||
Returns Left(failure)
|
||||
↓
|
||||
GetWarehousesUseCase returns Left(failure)
|
||||
↓
|
||||
WarehouseNotifier receives Left(failure)
|
||||
↓
|
||||
state = state.setError(failure.message)
|
||||
↓
|
||||
UI shows error state with retry button
|
||||
```
|
||||
|
||||
### 3. Warehouse Selection Flow
|
||||
|
||||
```
|
||||
User taps on WarehouseCard
|
||||
↓
|
||||
onTap callback triggered
|
||||
↓
|
||||
_onWarehouseSelected(warehouse)
|
||||
↓
|
||||
ref.read(warehouseProvider.notifier).selectWarehouse(warehouse)
|
||||
↓
|
||||
state = state.setSelectedWarehouse(warehouse)
|
||||
↓
|
||||
Navigation: context.push('/operations', extra: warehouse)
|
||||
↓
|
||||
OperationSelectionPage receives warehouse
|
||||
```
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Riverpod Providers │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ secureStorageProvider │
|
||||
│ ↓ │
|
||||
│ apiClientProvider │
|
||||
│ ↓ │
|
||||
│ warehouseRemoteDataSourceProvider │
|
||||
│ ↓ │
|
||||
│ warehouseRepositoryProvider │
|
||||
│ ↓ │
|
||||
│ getWarehousesUseCaseProvider │
|
||||
│ ↓ │
|
||||
│ warehouseProvider (StateNotifierProvider) │
|
||||
│ ↓ │
|
||||
│ WarehouseSelectionPage watches this provider │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## File Dependencies
|
||||
|
||||
```
|
||||
warehouse_selection_page.dart
|
||||
↓ imports
|
||||
- warehouse_entity.dart
|
||||
- warehouse_card.dart
|
||||
- warehouse_provider.dart (via DI setup)
|
||||
|
||||
warehouse_card.dart
|
||||
↓ imports
|
||||
- warehouse_entity.dart
|
||||
|
||||
warehouse_provider.dart
|
||||
↓ imports
|
||||
- warehouse_entity.dart
|
||||
- get_warehouses_usecase.dart
|
||||
|
||||
get_warehouses_usecase.dart
|
||||
↓ imports
|
||||
- warehouse_entity.dart
|
||||
- warehouse_repository.dart (interface)
|
||||
|
||||
warehouse_repository_impl.dart
|
||||
↓ imports
|
||||
- warehouse_entity.dart
|
||||
- warehouse_repository.dart (interface)
|
||||
- warehouse_remote_datasource.dart
|
||||
|
||||
warehouse_remote_datasource.dart
|
||||
↓ imports
|
||||
- warehouse_model.dart
|
||||
- api_client.dart
|
||||
- api_response.dart
|
||||
|
||||
warehouse_model.dart
|
||||
↓ imports
|
||||
- warehouse_entity.dart
|
||||
```
|
||||
|
||||
## State Transitions
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ Initial │
|
||||
│ isLoading: F │
|
||||
│ error: null │
|
||||
│ warehouses:[]│
|
||||
└──────────────┘
|
||||
↓
|
||||
loadWarehouses()
|
||||
↓
|
||||
┌──────────────┐
|
||||
│ Loading │
|
||||
│ isLoading: T │────────────────┐
|
||||
│ error: null │ │
|
||||
│ warehouses:[]│ │
|
||||
└──────────────┘ │
|
||||
↓ │
|
||||
Success Failure
|
||||
↓ ↓
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ Success │ │ Error │
|
||||
│ isLoading: F │ │ isLoading: F │
|
||||
│ error: null │ │ error: "..." │
|
||||
│ warehouses:[…]│ │ warehouses:[]│
|
||||
└──────────────┘ └──────────────┘
|
||||
↓ ↓
|
||||
Selection Retry
|
||||
↓ ↓
|
||||
┌──────────────┐ (back to Loading)
|
||||
│ Selected │
|
||||
│ selected: W │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
## API Response Parsing
|
||||
|
||||
```
|
||||
Raw API Response (JSON)
|
||||
↓
|
||||
{
|
||||
"Value": [
|
||||
{
|
||||
"Id": 1,
|
||||
"Name": "Warehouse A",
|
||||
"Code": "001",
|
||||
...
|
||||
}
|
||||
],
|
||||
"IsSuccess": true,
|
||||
...
|
||||
}
|
||||
↓
|
||||
ApiResponse.fromJson() parses wrapper
|
||||
↓
|
||||
ApiResponse<List<WarehouseModel>> {
|
||||
value: [WarehouseModel, WarehouseModel, ...],
|
||||
isSuccess: true,
|
||||
isFailure: false,
|
||||
errors: [],
|
||||
errorCodes: []
|
||||
}
|
||||
↓
|
||||
Check isSuccess
|
||||
↓
|
||||
if (isSuccess && value != null)
|
||||
return value!
|
||||
else
|
||||
throw ServerException(errors.first)
|
||||
↓
|
||||
List<WarehouseModel>
|
||||
↓
|
||||
map((model) => model.toEntity())
|
||||
↓
|
||||
List<WarehouseEntity>
|
||||
```
|
||||
|
||||
## Separation of Concerns
|
||||
|
||||
### Domain Layer
|
||||
- **No dependencies** on Flutter, Dio, or other frameworks
|
||||
- Contains **pure business logic**
|
||||
- Defines **contracts** (repository interfaces)
|
||||
- **Independent** and **testable**
|
||||
|
||||
### Data Layer
|
||||
- **Implements** domain contracts
|
||||
- Handles **external dependencies** (API, database)
|
||||
- **Converts** between models and entities
|
||||
- **Transforms** exceptions to failures
|
||||
|
||||
### Presentation Layer
|
||||
- **Depends** only on domain layer
|
||||
- Handles **UI rendering** and **user interactions**
|
||||
- Manages **local state** with Riverpod
|
||||
- **Observes** changes and **reacts** to state updates
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
```
|
||||
Unit Tests
|
||||
├── Domain Layer
|
||||
│ ├── Test entities (equality, methods)
|
||||
│ ├── Test use cases (mock repository)
|
||||
│ └── Verify business logic
|
||||
├── Data Layer
|
||||
│ ├── Test models (JSON serialization)
|
||||
│ ├── Test data sources (mock ApiClient)
|
||||
│ └── Test repository (mock data source)
|
||||
└── Presentation Layer
|
||||
├── Test notifier (mock use case)
|
||||
└── Test state transitions
|
||||
|
||||
Widget Tests
|
||||
├── Test UI rendering
|
||||
├── Test user interactions
|
||||
└── Test state-based UI changes
|
||||
|
||||
Integration Tests
|
||||
├── Test complete flow
|
||||
└── Test with real dependencies
|
||||
```
|
||||
|
||||
## Benefits of This Architecture
|
||||
|
||||
1. **Testability**: Each layer can be tested independently with mocks
|
||||
2. **Maintainability**: Changes in one layer don't affect others
|
||||
3. **Scalability**: Easy to add new features following the same pattern
|
||||
4. **Reusability**: Domain entities and use cases can be reused
|
||||
5. **Separation**: Clear boundaries between UI, business logic, and data
|
||||
6. **Flexibility**: Easy to swap implementations (e.g., change API client)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-10-27
|
||||
**Version:** 1.0.0
|
||||
@@ -1,649 +0,0 @@
|
||||
# Warehouse Feature
|
||||
|
||||
Complete implementation of the warehouse feature following **Clean Architecture** principles.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
This feature follows a three-layer clean architecture pattern:
|
||||
|
||||
```
|
||||
Presentation Layer (UI)
|
||||
↓ (uses)
|
||||
Domain Layer (Business Logic)
|
||||
↓ (uses)
|
||||
Data Layer (API & Data Sources)
|
||||
```
|
||||
|
||||
### Key Principles
|
||||
|
||||
- **Separation of Concerns**: Each layer has a single responsibility
|
||||
- **Dependency Inversion**: Outer layers depend on inner layers, not vice versa
|
||||
- **Testability**: Each layer can be tested independently
|
||||
- **Maintainability**: Changes in one layer don't affect others
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
lib/features/warehouse/
|
||||
├── data/
|
||||
│ ├── datasources/
|
||||
│ │ └── warehouse_remote_datasource.dart # API calls using ApiClient
|
||||
│ ├── models/
|
||||
│ │ └── warehouse_model.dart # Data transfer objects with JSON serialization
|
||||
│ └── repositories/
|
||||
│ └── warehouse_repository_impl.dart # Repository implementation
|
||||
├── domain/
|
||||
│ ├── entities/
|
||||
│ │ └── warehouse_entity.dart # Pure business models
|
||||
│ ├── repositories/
|
||||
│ │ └── warehouse_repository.dart # Repository interface/contract
|
||||
│ └── usecases/
|
||||
│ └── get_warehouses_usecase.dart # Business logic use cases
|
||||
├── presentation/
|
||||
│ ├── pages/
|
||||
│ │ └── warehouse_selection_page.dart # Main warehouse selection screen
|
||||
│ ├── providers/
|
||||
│ │ └── warehouse_provider.dart # Riverpod state management
|
||||
│ └── widgets/
|
||||
│ └── warehouse_card.dart # Reusable warehouse card widget
|
||||
├── warehouse_exports.dart # Barrel file for clean imports
|
||||
├── warehouse_provider_setup_example.dart # Provider setup guide
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Layer Details
|
||||
|
||||
### 1. Domain Layer (Core Business Logic)
|
||||
|
||||
The innermost layer that contains business entities, repository interfaces, and use cases. **No dependencies on external frameworks or packages** (except dartz for Either).
|
||||
|
||||
#### Entities
|
||||
|
||||
`domain/entities/warehouse_entity.dart`
|
||||
|
||||
- Pure Dart class representing a warehouse
|
||||
- No JSON serialization logic
|
||||
- Contains business rules and validations
|
||||
- Extends Equatable for value comparison
|
||||
|
||||
```dart
|
||||
class WarehouseEntity extends Equatable {
|
||||
final int id;
|
||||
final String name;
|
||||
final String code;
|
||||
final String? description;
|
||||
final bool isNGWareHouse;
|
||||
final int totalCount;
|
||||
|
||||
bool get hasItems => totalCount > 0;
|
||||
bool get isNGType => isNGWareHouse;
|
||||
}
|
||||
```
|
||||
|
||||
#### Repository Interface
|
||||
|
||||
`domain/repositories/warehouse_repository.dart`
|
||||
|
||||
- Abstract interface defining data operations
|
||||
- Returns `Either<Failure, T>` for error handling
|
||||
- Implementation is provided by the data layer
|
||||
|
||||
```dart
|
||||
abstract class WarehouseRepository {
|
||||
Future<Either<Failure, List<WarehouseEntity>>> getWarehouses();
|
||||
}
|
||||
```
|
||||
|
||||
#### Use Cases
|
||||
|
||||
`domain/usecases/get_warehouses_usecase.dart`
|
||||
|
||||
- Single responsibility: fetch warehouses
|
||||
- Encapsulates business logic
|
||||
- Depends only on repository interface
|
||||
|
||||
```dart
|
||||
class GetWarehousesUseCase {
|
||||
final WarehouseRepository repository;
|
||||
|
||||
Future<Either<Failure, List<WarehouseEntity>>> call() async {
|
||||
return await repository.getWarehouses();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Data Layer (External Data Management)
|
||||
|
||||
Handles all data operations including API calls, JSON serialization, and error handling.
|
||||
|
||||
#### Models
|
||||
|
||||
`data/models/warehouse_model.dart`
|
||||
|
||||
- Extends domain entity
|
||||
- Adds JSON serialization (`fromJson`, `toJson`)
|
||||
- Maps API response format to domain entities
|
||||
- Matches API field naming (PascalCase)
|
||||
|
||||
```dart
|
||||
class WarehouseModel extends WarehouseEntity {
|
||||
factory WarehouseModel.fromJson(Map<String, dynamic> json) {
|
||||
return WarehouseModel(
|
||||
id: json['Id'] ?? 0,
|
||||
name: json['Name'] ?? '',
|
||||
code: json['Code'] ?? '',
|
||||
description: json['Description'],
|
||||
isNGWareHouse: json['IsNGWareHouse'] ?? false,
|
||||
totalCount: json['TotalCount'] ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Data Sources
|
||||
|
||||
`data/datasources/warehouse_remote_datasource.dart`
|
||||
|
||||
- Interface + implementation pattern
|
||||
- Makes API calls using `ApiClient`
|
||||
- Parses `ApiResponse` wrapper
|
||||
- Throws typed exceptions (`ServerException`, `NetworkException`)
|
||||
|
||||
```dart
|
||||
class WarehouseRemoteDataSourceImpl implements WarehouseRemoteDataSource {
|
||||
Future<List<WarehouseModel>> getWarehouses() async {
|
||||
final response = await apiClient.get('/warehouses');
|
||||
final apiResponse = ApiResponse.fromJson(
|
||||
response.data,
|
||||
(json) => (json as List).map((e) => WarehouseModel.fromJson(e)).toList(),
|
||||
);
|
||||
|
||||
if (apiResponse.isSuccess && apiResponse.value != null) {
|
||||
return apiResponse.value!;
|
||||
} else {
|
||||
throw ServerException(apiResponse.errors.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Repository Implementation
|
||||
|
||||
`data/repositories/warehouse_repository_impl.dart`
|
||||
|
||||
- Implements domain repository interface
|
||||
- Coordinates data sources
|
||||
- Converts exceptions to failures
|
||||
- Maps models to entities
|
||||
|
||||
```dart
|
||||
class WarehouseRepositoryImpl implements WarehouseRepository {
|
||||
@override
|
||||
Future<Either<Failure, List<WarehouseEntity>>> getWarehouses() async {
|
||||
try {
|
||||
final warehouses = await remoteDataSource.getWarehouses();
|
||||
final entities = warehouses.map((model) => model.toEntity()).toList();
|
||||
return Right(entities);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} on NetworkException catch (e) {
|
||||
return Left(NetworkFailure(e.message));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Presentation Layer (UI & State Management)
|
||||
|
||||
Handles UI rendering, user interactions, and state management using Riverpod.
|
||||
|
||||
#### State Management
|
||||
|
||||
`presentation/providers/warehouse_provider.dart`
|
||||
|
||||
- `WarehouseState`: Immutable state class
|
||||
- `warehouses`: List of warehouses
|
||||
- `selectedWarehouse`: Currently selected warehouse
|
||||
- `isLoading`: Loading indicator
|
||||
- `error`: Error message
|
||||
|
||||
- `WarehouseNotifier`: StateNotifier managing state
|
||||
- `loadWarehouses()`: Fetch warehouses from API
|
||||
- `selectWarehouse()`: Select a warehouse
|
||||
- `refresh()`: Reload warehouses
|
||||
- `clearError()`: Clear error state
|
||||
|
||||
```dart
|
||||
class WarehouseState {
|
||||
final List<WarehouseEntity> warehouses;
|
||||
final WarehouseEntity? selectedWarehouse;
|
||||
final bool isLoading;
|
||||
final String? error;
|
||||
}
|
||||
|
||||
class WarehouseNotifier extends StateNotifier<WarehouseState> {
|
||||
Future<void> loadWarehouses() async {
|
||||
state = state.setLoading();
|
||||
final result = await getWarehousesUseCase();
|
||||
result.fold(
|
||||
(failure) => state = state.setError(failure.message),
|
||||
(warehouses) => state = state.setSuccess(warehouses),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Pages
|
||||
|
||||
`presentation/pages/warehouse_selection_page.dart`
|
||||
|
||||
- ConsumerStatefulWidget using Riverpod
|
||||
- Loads warehouses on initialization
|
||||
- Displays different UI states:
|
||||
- Loading: CircularProgressIndicator
|
||||
- Error: Error message with retry button
|
||||
- Empty: No warehouses message
|
||||
- Success: List of warehouse cards
|
||||
- Pull-to-refresh support
|
||||
- Navigation to operations page on selection
|
||||
|
||||
#### Widgets
|
||||
|
||||
`presentation/widgets/warehouse_card.dart`
|
||||
|
||||
- Reusable warehouse card component
|
||||
- Displays:
|
||||
- Warehouse name (title)
|
||||
- Code (with QR icon)
|
||||
- Total items count (with inventory icon)
|
||||
- Description (if available)
|
||||
- NG warehouse badge (if applicable)
|
||||
- Material Design 3 styling
|
||||
- Tap to select functionality
|
||||
|
||||
## API Integration
|
||||
|
||||
### Endpoint
|
||||
|
||||
```
|
||||
GET /warehouses
|
||||
```
|
||||
|
||||
### Request
|
||||
|
||||
```bash
|
||||
curl -X GET https://api.example.com/warehouses \
|
||||
-H "Authorization: Bearer {access_token}"
|
||||
```
|
||||
|
||||
### Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"Value": [
|
||||
{
|
||||
"Id": 1,
|
||||
"Name": "Kho nguyên vật liệu",
|
||||
"Code": "001",
|
||||
"Description": "Kho chứa nguyên vật liệu",
|
||||
"IsNGWareHouse": false,
|
||||
"TotalCount": 8
|
||||
},
|
||||
{
|
||||
"Id": 2,
|
||||
"Name": "Kho bán thành phẩm công đoạn",
|
||||
"Code": "002",
|
||||
"Description": null,
|
||||
"IsNGWareHouse": false,
|
||||
"TotalCount": 12
|
||||
}
|
||||
],
|
||||
"IsSuccess": true,
|
||||
"IsFailure": false,
|
||||
"Errors": [],
|
||||
"ErrorCodes": []
|
||||
}
|
||||
```
|
||||
|
||||
## Setup & Integration
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
Ensure your `pubspec.yaml` includes:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
flutter_riverpod: ^2.4.9
|
||||
dio: ^5.3.2
|
||||
dartz: ^0.10.1
|
||||
equatable: ^2.0.5
|
||||
flutter_secure_storage: ^9.0.0
|
||||
```
|
||||
|
||||
### 2. Set Up Providers
|
||||
|
||||
Create or update your provider configuration file (e.g., `lib/core/di/providers.dart`):
|
||||
|
||||
```dart
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../features/warehouse/warehouse_exports.dart';
|
||||
|
||||
// Core providers (if not already set up)
|
||||
final secureStorageProvider = Provider<SecureStorage>((ref) {
|
||||
return SecureStorage();
|
||||
});
|
||||
|
||||
final apiClientProvider = Provider<ApiClient>((ref) {
|
||||
final secureStorage = ref.watch(secureStorageProvider);
|
||||
return ApiClient(secureStorage);
|
||||
});
|
||||
|
||||
// Warehouse data layer providers
|
||||
final warehouseRemoteDataSourceProvider = Provider<WarehouseRemoteDataSource>((ref) {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
return WarehouseRemoteDataSourceImpl(apiClient);
|
||||
});
|
||||
|
||||
final warehouseRepositoryProvider = Provider((ref) {
|
||||
final remoteDataSource = ref.watch(warehouseRemoteDataSourceProvider);
|
||||
return WarehouseRepositoryImpl(remoteDataSource);
|
||||
});
|
||||
|
||||
// Warehouse domain layer providers
|
||||
final getWarehousesUseCaseProvider = Provider((ref) {
|
||||
final repository = ref.watch(warehouseRepositoryProvider);
|
||||
return GetWarehousesUseCase(repository);
|
||||
});
|
||||
|
||||
// Warehouse presentation layer providers
|
||||
final warehouseProvider = StateNotifierProvider<WarehouseNotifier, WarehouseState>((ref) {
|
||||
final getWarehousesUseCase = ref.watch(getWarehousesUseCaseProvider);
|
||||
return WarehouseNotifier(getWarehousesUseCase);
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Update WarehouseSelectionPage
|
||||
|
||||
Replace the TODO comments in `warehouse_selection_page.dart`:
|
||||
|
||||
```dart
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.read(warehouseProvider.notifier).loadWarehouses();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final state = ref.watch(warehouseProvider);
|
||||
|
||||
// Rest of the implementation...
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Add to Router
|
||||
|
||||
Using go_router:
|
||||
|
||||
```dart
|
||||
GoRoute(
|
||||
path: '/warehouses',
|
||||
name: 'warehouses',
|
||||
builder: (context, state) => const WarehouseSelectionPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/operations',
|
||||
name: 'operations',
|
||||
builder: (context, state) {
|
||||
final warehouse = state.extra as WarehouseEntity;
|
||||
return OperationSelectionPage(warehouse: warehouse);
|
||||
},
|
||||
),
|
||||
```
|
||||
|
||||
### 5. Navigate to Warehouse Page
|
||||
|
||||
```dart
|
||||
// From login page after successful authentication
|
||||
context.go('/warehouses');
|
||||
|
||||
// Or using Navigator
|
||||
Navigator.of(context).pushNamed('/warehouses');
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Loading Warehouses
|
||||
|
||||
```dart
|
||||
// In a widget
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
ref.read(warehouseProvider.notifier).loadWarehouses();
|
||||
},
|
||||
child: const Text('Load Warehouses'),
|
||||
)
|
||||
```
|
||||
|
||||
### Watching State
|
||||
|
||||
```dart
|
||||
// In a ConsumerWidget
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(warehouseProvider);
|
||||
|
||||
if (state.isLoading) {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
|
||||
if (state.error != null) {
|
||||
return Text('Error: ${state.error}');
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: state.warehouses.length,
|
||||
itemBuilder: (context, index) {
|
||||
final warehouse = state.warehouses[index];
|
||||
return ListTile(title: Text(warehouse.name));
|
||||
},
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Selecting a Warehouse
|
||||
|
||||
```dart
|
||||
// Select warehouse and navigate
|
||||
void onWarehouseTap(WarehouseEntity warehouse) {
|
||||
ref.read(warehouseProvider.notifier).selectWarehouse(warehouse);
|
||||
context.push('/operations', extra: warehouse);
|
||||
}
|
||||
```
|
||||
|
||||
### Pull to Refresh
|
||||
|
||||
```dart
|
||||
RefreshIndicator(
|
||||
onRefresh: () => ref.read(warehouseProvider.notifier).refresh(),
|
||||
child: ListView(...),
|
||||
)
|
||||
```
|
||||
|
||||
### Accessing Selected Warehouse
|
||||
|
||||
```dart
|
||||
// In another page
|
||||
final state = ref.watch(warehouseProvider);
|
||||
final selectedWarehouse = state.selectedWarehouse;
|
||||
|
||||
if (selectedWarehouse != null) {
|
||||
Text('Current: ${selectedWarehouse.name}');
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The feature uses dartz's `Either` type for functional error handling:
|
||||
|
||||
```dart
|
||||
// In use case or repository
|
||||
Future<Either<Failure, List<WarehouseEntity>>> getWarehouses() async {
|
||||
try {
|
||||
final warehouses = await remoteDataSource.getWarehouses();
|
||||
return Right(warehouses); // Success
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message)); // Failure
|
||||
}
|
||||
}
|
||||
|
||||
// In presentation layer
|
||||
result.fold(
|
||||
(failure) => print('Error: ${failure.message}'),
|
||||
(warehouses) => print('Success: ${warehouses.length} items'),
|
||||
);
|
||||
```
|
||||
|
||||
### Failure Types
|
||||
|
||||
- `ServerFailure`: API errors, HTTP errors
|
||||
- `NetworkFailure`: Connection issues, timeouts
|
||||
- `CacheFailure`: Local storage errors (if implemented)
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Tests
|
||||
|
||||
**Test Use Case:**
|
||||
```dart
|
||||
test('should get warehouses from repository', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getWarehouses())
|
||||
.thenAnswer((_) async => Right(mockWarehouses));
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, Right(mockWarehouses));
|
||||
verify(mockRepository.getWarehouses());
|
||||
});
|
||||
```
|
||||
|
||||
**Test Repository:**
|
||||
```dart
|
||||
test('should return warehouses when remote call is successful', () async {
|
||||
// Arrange
|
||||
when(mockRemoteDataSource.getWarehouses())
|
||||
.thenAnswer((_) async => mockWarehouseModels);
|
||||
|
||||
// Act
|
||||
final result = await repository.getWarehouses();
|
||||
|
||||
// Assert
|
||||
expect(result.isRight(), true);
|
||||
});
|
||||
```
|
||||
|
||||
**Test Notifier:**
|
||||
```dart
|
||||
test('should emit loading then success when warehouses are loaded', () async {
|
||||
// Arrange
|
||||
when(mockUseCase()).thenAnswer((_) async => Right(mockWarehouses));
|
||||
|
||||
// Act
|
||||
await notifier.loadWarehouses();
|
||||
|
||||
// Assert
|
||||
expect(notifier.state.isLoading, false);
|
||||
expect(notifier.state.warehouses, mockWarehouses);
|
||||
});
|
||||
```
|
||||
|
||||
### Widget Tests
|
||||
|
||||
```dart
|
||||
testWidgets('should display warehouse list when loaded', (tester) async {
|
||||
// Arrange
|
||||
final container = ProviderContainer(
|
||||
overrides: [
|
||||
warehouseProvider.overrideWith((ref) => MockWarehouseNotifier()),
|
||||
],
|
||||
);
|
||||
|
||||
// Act
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: const WarehouseSelectionPage(),
|
||||
),
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(find.byType(WarehouseCard), findsWidgets);
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use Either for error handling** - Don't throw exceptions across layers
|
||||
2. **Keep domain layer pure** - No Flutter/external dependencies
|
||||
3. **Use value objects** - Entities should be immutable
|
||||
4. **Single responsibility** - Each class has one reason to change
|
||||
5. **Dependency inversion** - Depend on abstractions, not concretions
|
||||
6. **Test each layer independently** - Use mocks and test doubles
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Provider Not Found
|
||||
|
||||
**Error:** `ProviderNotFoundException`
|
||||
|
||||
**Solution:** Make sure you've set up all providers in your provider configuration file and wrapped your app with `ProviderScope`.
|
||||
|
||||
### Null Safety Issues
|
||||
|
||||
**Error:** `Null check operator used on a null value`
|
||||
|
||||
**Solution:** Always check for null before accessing optional fields:
|
||||
```dart
|
||||
if (warehouse.description != null) {
|
||||
Text(warehouse.description!);
|
||||
}
|
||||
```
|
||||
|
||||
### API Response Format Mismatch
|
||||
|
||||
**Error:** `ServerException: Invalid response format`
|
||||
|
||||
**Solution:** Verify that the API response matches the expected format in `ApiResponse.fromJson` and `WarehouseModel.fromJson`.
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- [ ] Add caching with Hive for offline support
|
||||
- [ ] Implement warehouse search functionality
|
||||
- [ ] Add warehouse filtering (by type, name, etc.)
|
||||
- [ ] Add pagination for large warehouse lists
|
||||
- [ ] Implement warehouse CRUD operations
|
||||
- [ ] Add warehouse analytics and statistics
|
||||
|
||||
## Related Features
|
||||
|
||||
- **Authentication**: `/lib/features/auth/` - User login and token management
|
||||
- **Operations**: `/lib/features/operation/` - Import/Export selection
|
||||
- **Products**: `/lib/features/products/` - Product listing per warehouse
|
||||
|
||||
## References
|
||||
|
||||
- [Clean Architecture by Uncle Bob](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
|
||||
- [Flutter Riverpod Documentation](https://riverpod.dev/)
|
||||
- [Dartz Package for Functional Programming](https://pub.dev/packages/dartz)
|
||||
- [Material Design 3](https://m3.material.io/)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-10-27
|
||||
**Version:** 1.0.0
|
||||
**Author:** Claude Code
|
||||
@@ -174,7 +174,7 @@ class _WarehouseSelectionPageState
|
||||
ref.read(warehouseProvider.notifier).selectWarehouse(warehouse);
|
||||
|
||||
// Navigate to operations page
|
||||
context.go('/operations', extra: warehouse);
|
||||
context.push('/operations', extra: warehouse);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,396 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../domain/entities/warehouse_entity.dart';
|
||||
import '../widgets/warehouse_card.dart';
|
||||
import '../../../../core/router/app_router.dart';
|
||||
|
||||
/// EXAMPLE: Warehouse selection page with proper navigation integration
|
||||
///
|
||||
/// This is a complete example showing how to integrate the warehouse selection
|
||||
/// page with the new router. Use this as a reference when implementing the
|
||||
/// actual warehouse provider and state management.
|
||||
///
|
||||
/// Key Features:
|
||||
/// - Uses type-safe navigation with extension methods
|
||||
/// - Proper error handling
|
||||
/// - Loading states
|
||||
/// - Pull to refresh
|
||||
/// - Integration with router
|
||||
class WarehouseSelectionPageExample extends ConsumerStatefulWidget {
|
||||
const WarehouseSelectionPageExample({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<WarehouseSelectionPageExample> createState() =>
|
||||
_WarehouseSelectionPageExampleState();
|
||||
}
|
||||
|
||||
class _WarehouseSelectionPageExampleState
|
||||
extends ConsumerState<WarehouseSelectionPageExample> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Load warehouses when page is first created
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// TODO: Replace with actual provider
|
||||
// ref.read(warehouseProvider.notifier).loadWarehouses();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// TODO: Replace with actual provider
|
||||
// final state = ref.watch(warehouseProvider);
|
||||
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Select Warehouse'),
|
||||
backgroundColor: colorScheme.primaryContainer,
|
||||
foregroundColor: colorScheme.onPrimaryContainer,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.logout),
|
||||
onPressed: () => _handleLogout(context),
|
||||
tooltip: 'Logout',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _buildBody(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(BuildContext context) {
|
||||
// For demonstration, showing example warehouse list
|
||||
// In actual implementation, use state from provider:
|
||||
// if (state.isLoading) return _buildLoadingState();
|
||||
// if (state.error != null) return _buildErrorState(context, state.error!);
|
||||
// if (!state.hasWarehouses) return _buildEmptyState(context);
|
||||
// return _buildWarehouseList(state.warehouses);
|
||||
|
||||
// Example warehouses for demonstration
|
||||
final exampleWarehouses = _getExampleWarehouses();
|
||||
return _buildWarehouseList(exampleWarehouses);
|
||||
}
|
||||
|
||||
/// Build loading state UI
|
||||
Widget _buildLoadingState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Loading warehouses...',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build error state UI
|
||||
Widget _buildErrorState(BuildContext context, String error) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 64,
|
||||
color: colorScheme.error,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Error Loading Warehouses',
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
color: colorScheme.error,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
error,
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
FilledButton.icon(
|
||||
onPressed: () {
|
||||
// TODO: Replace with actual provider
|
||||
// ref.read(warehouseProvider.notifier).loadWarehouses();
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Retry'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build empty state UI
|
||||
Widget _buildEmptyState(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.inventory_2_outlined,
|
||||
size: 64,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'No Warehouses Available',
|
||||
style: theme.textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'There are no warehouses to display.',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
// TODO: Replace with actual provider
|
||||
// ref.read(warehouseProvider.notifier).loadWarehouses();
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Refresh'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build warehouse list UI
|
||||
Widget _buildWarehouseList(List<WarehouseEntity> warehouses) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
// TODO: Replace with actual provider
|
||||
// await ref.read(warehouseProvider.notifier).refresh();
|
||||
},
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
itemCount: warehouses.length,
|
||||
itemBuilder: (context, index) {
|
||||
final warehouse = warehouses[index];
|
||||
return WarehouseCard(
|
||||
warehouse: warehouse,
|
||||
onTap: () => _onWarehouseSelected(context, warehouse),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Handle warehouse selection
|
||||
///
|
||||
/// This is the key integration point with the router!
|
||||
/// Uses the type-safe extension method to navigate to operations page
|
||||
void _onWarehouseSelected(BuildContext context, WarehouseEntity warehouse) {
|
||||
// TODO: Optionally save selected warehouse to provider
|
||||
// ref.read(warehouseProvider.notifier).selectWarehouse(warehouse);
|
||||
|
||||
// Navigate to operations page using type-safe extension method
|
||||
context.goToOperations(warehouse);
|
||||
}
|
||||
|
||||
/// Handle logout
|
||||
void _handleLogout(BuildContext context) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Logout'),
|
||||
content: const Text('Are you sure you want to logout?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: const Text('Logout'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed == true && context.mounted) {
|
||||
// TODO: Call logout from auth provider
|
||||
// await ref.read(authProvider.notifier).logout();
|
||||
|
||||
// Router will automatically redirect to login page
|
||||
// due to authentication state change
|
||||
}
|
||||
}
|
||||
|
||||
/// Get example warehouses for demonstration
|
||||
List<WarehouseEntity> _getExampleWarehouses() {
|
||||
return [
|
||||
WarehouseEntity(
|
||||
id: 1,
|
||||
name: 'Kho nguyên vật liệu',
|
||||
code: '001',
|
||||
description: 'Warehouse for raw materials',
|
||||
isNGWareHouse: false,
|
||||
totalCount: 8,
|
||||
),
|
||||
WarehouseEntity(
|
||||
id: 2,
|
||||
name: 'Kho bán thành phẩm công đoạn',
|
||||
code: '002',
|
||||
description: 'Semi-finished goods warehouse',
|
||||
isNGWareHouse: false,
|
||||
totalCount: 8,
|
||||
),
|
||||
WarehouseEntity(
|
||||
id: 3,
|
||||
name: 'Kho thành phẩm',
|
||||
code: '003',
|
||||
description: 'Finished goods warehouse',
|
||||
isNGWareHouse: false,
|
||||
totalCount: 8,
|
||||
),
|
||||
WarehouseEntity(
|
||||
id: 4,
|
||||
name: 'Kho tiêu hao',
|
||||
code: '004',
|
||||
description: 'Để chứa phụ tùng',
|
||||
isNGWareHouse: false,
|
||||
totalCount: 8,
|
||||
),
|
||||
WarehouseEntity(
|
||||
id: 5,
|
||||
name: 'Kho NG',
|
||||
code: '005',
|
||||
description: 'Non-conforming products warehouse',
|
||||
isNGWareHouse: true,
|
||||
totalCount: 3,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/// EXAMPLE: Alternative approach using named routes
|
||||
///
|
||||
/// This shows how to use named routes instead of path-based navigation
|
||||
class WarehouseSelectionWithNamedRoutesExample extends ConsumerWidget {
|
||||
const WarehouseSelectionWithNamedRoutesExample({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// Example warehouse for demonstration
|
||||
final warehouse = WarehouseEntity(
|
||||
id: 1,
|
||||
name: 'Example Warehouse',
|
||||
code: '001',
|
||||
isNGWareHouse: false,
|
||||
totalCount: 10,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Named Routes Example')),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// Using named route navigation
|
||||
context.goToOperationsNamed(warehouse);
|
||||
},
|
||||
child: const Text('Go to Operations (Named)'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// Using path-based navigation
|
||||
context.goToOperations(warehouse);
|
||||
},
|
||||
child: const Text('Go to Operations (Path)'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// EXAMPLE: Navigation from operation to products
|
||||
///
|
||||
/// Shows how to navigate from operation selection to products page
|
||||
class OperationNavigationExample extends StatelessWidget {
|
||||
final WarehouseEntity warehouse;
|
||||
|
||||
const OperationNavigationExample({
|
||||
super.key,
|
||||
required this.warehouse,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Operation Navigation Example')),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// Navigate to products with import operation
|
||||
context.goToProducts(
|
||||
warehouse: warehouse,
|
||||
operationType: 'import',
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.arrow_downward),
|
||||
label: const Text('Import Products'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// Navigate to products with export operation
|
||||
context.goToProducts(
|
||||
warehouse: warehouse,
|
||||
operationType: 'export',
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.arrow_upward),
|
||||
label: const Text('Export Products'),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
// Navigate back
|
||||
context.goBack();
|
||||
},
|
||||
child: const Text('Go Back'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user