fix
This commit is contained in:
@@ -53,11 +53,18 @@ class ApiEndpoints {
|
|||||||
|
|
||||||
// ==================== Product Endpoints ====================
|
// ==================== Product Endpoints ====================
|
||||||
|
|
||||||
/// Get products for a warehouse
|
/// Get products for import (all products)
|
||||||
/// GET: /portalProduct/getAllProduct (requires auth token)
|
/// GET: /portalProduct/getAllProduct (requires auth token)
|
||||||
/// Response: List of products
|
/// Response: List of products
|
||||||
static const String products = '/portalProduct/getAllProduct';
|
static const String products = '/portalProduct/getAllProduct';
|
||||||
|
|
||||||
|
/// Get products for export (products in specific warehouse)
|
||||||
|
/// GET: /portalWareHouse/GetAllProductsInWareHouse?warehouseId={id} (requires auth token)
|
||||||
|
/// Query param: warehouseId (int)
|
||||||
|
/// Response: List of products in warehouse
|
||||||
|
static String productsForExport(int warehouseId) =>
|
||||||
|
'/portalWareHouse/GetAllProductsInWareHouse?warehouseId=$warehouseId';
|
||||||
|
|
||||||
/// Get product stage in warehouse
|
/// Get product stage in warehouse
|
||||||
/// POST: /portalWareHouse/GetProductStageInWareHouse
|
/// POST: /portalWareHouse/GetProductStageInWareHouse
|
||||||
/// Body: { "WareHouseId": int, "ProductId": int }
|
/// Body: { "WareHouseId": int, "ProductId": int }
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ curl --request POST \
|
|||||||
}'
|
}'
|
||||||
|
|
||||||
|
|
||||||
#Get products
|
#Get products for import
|
||||||
curl --request GET \
|
curl --request GET \
|
||||||
--url https://dotnet.elidev.info:8157/ws/portalProduct/getAllProduct \
|
--url https://dotnet.elidev.info:8157/ws/portalProduct/getAllProduct \
|
||||||
--compressed \
|
--compressed \
|
||||||
@@ -54,6 +54,22 @@ curl --request GET \
|
|||||||
--header 'Sec-Fetch-Site: same-site' \
|
--header 'Sec-Fetch-Site: same-site' \
|
||||||
--header 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:144.0) Gecko/20100101 Firefox/144.0'
|
--header 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:144.0) Gecko/20100101 Firefox/144.0'
|
||||||
|
|
||||||
|
|
||||||
|
#Get product for export
|
||||||
|
curl 'https://dotnet.elidev.info:8157/ws/portalWareHouse/GetAllProductsInWareHouse?warehouseId=1' \
|
||||||
|
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:144.0) Gecko/20100101 Firefox/144.0' \
|
||||||
|
-H 'Accept: application/json, text/plain, */*' \
|
||||||
|
-H 'Accept-Language: en-US,en;q=0.5' \
|
||||||
|
-H 'Accept-Encoding: gzip, deflate, br, zstd' \
|
||||||
|
-H 'AccessToken: 1k5fXyQVXtGkfjwS4g2USldGyLSA7Zwa2jdj5tLe+3YfSDlk02aYgqsFh5xArkdL4N529x7IYJOGrLJJgLBPNVgD51zFfEYBzfJmMH2RUm7iegvDJaMCLISySw0zd6kcsqeJi7vtuybgY2NDPxDgiSOj4wX417PzB8AVg5bl1ZAAJ3LcVAqqtA1PDTU5ZU1QQYapNeBNxAHjnd2ojTZK1GJBIyY5Gd8P9gB880ppAKq8manNMZYsa4d8tkYf0SJUul2aqLIWJAwDGORpPmfjqkN4hMh85xAfPTZi6m4DdI0u2rHDMLaZ8eIsV16qA8wimSDnWi0VeG0SZ4ugbCdJAi3t1/uICTftiy06PJEkulBLV+h2xS/7SlmEY2xoN5ISi++3FNqsFPGa9QH6akGu2C7IXEUBCg3iGJx0uL+vULmVqk5OJIXdqiKVQ366hvhPlK2AM1zbh49x/ngibe08483WTL5uAY/fsKuBxQCpTc2368Gqhpd7QRtZFKpzikhyTWsR3nQIi6ExSstCeFbe8ehgo0PuTPZNHH5IHTc49snH6IZrSbR+F62Wu/D+4DlvMTK/ktG6LVQ3r3jSJC5MAQDV5Q9WK3RvsWMPvZrsaVW/Exz0GBgWP4W0adADg7MFSlnGDOJm6I4fCLHZIJCUww50L6iNmzvrdibrQT5jKACVgNquMZCfeZlf3m2BwUx9T6J45lAePpJ+QaMh+2voFqRiOLi98MLqOG6TW7z96sadzFVR9YU1xwM51jQDjnUlrXt0+msq29Jqt8LoCyQsG4r3RgS/tUJhximq11MDXsSXanpYM7jesjr8mAG4qjYN6z6c1Gl5N0dhcDF4HeEaIlNIgZ75FqtXZnLqvhHPyk6L2iR2ZT15nobZxLzOUad4a0OymUDUv7xuEBdEk5kmzZLDpbOxrKiyMpGSlbBhEoBMoA0u6ZKtBGQfCJ02s6Ri0WhLLM4XJCjGrpoEkTUuZ7YG39Zva19HGV0kkxeFYkG0lnZBO6jCggem5f+S2NQvXP/kUrWX1GeQFCq5PScvwJexLsbh0LKC2MGovkecoBKtNIK21V6ztvWL8lThJAl9' \
|
||||||
|
-H 'AppID: Minhthu2016' \
|
||||||
|
-H 'Origin: https://dotnet.elidev.info:8158' \
|
||||||
|
-H 'Connection: keep-alive' \
|
||||||
|
-H 'Referer: https://dotnet.elidev.info:8158/' \
|
||||||
|
-H 'Sec-Fetch-Dest: empty' \
|
||||||
|
-H 'Sec-Fetch-Mode: cors' \
|
||||||
|
-H 'Sec-Fetch-Site: same-site'
|
||||||
|
|
||||||
#Get product by id
|
#Get product by id
|
||||||
curl --request POST \
|
curl --request POST \
|
||||||
--url https://dotnet.elidev.info:8157/ws/portalWareHouse/GetProductStageInWareHouse \
|
--url https://dotnet.elidev.info:8157/ws/portalWareHouse/GetProductStageInWareHouse \
|
||||||
@@ -76,3 +92,22 @@ curl --request POST \
|
|||||||
"WareHouseId": 7,
|
"WareHouseId": 7,
|
||||||
"ProductId": 11
|
"ProductId": 11
|
||||||
}'
|
}'
|
||||||
|
|
||||||
|
#Create import product
|
||||||
|
curl 'https://dotnet.elidev.info:8157/ws/portalWareHouse/createProductWareHouse' \
|
||||||
|
-X POST \
|
||||||
|
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:144.0) Gecko/20100101 Firefox/144.0' \
|
||||||
|
-H 'Accept: application/json, text/plain, */*' \
|
||||||
|
-H 'Accept-Language: en-US,en;q=0.5' \
|
||||||
|
-H 'Accept-Encoding: gzip, deflate, br, zstd' \
|
||||||
|
-H 'AccessToken: 1k5fXyQVXtGkfjwS4g2USldGyLSA7Zwa2jdj5tLe+3YfSDlk02aYgqsFh5xArkdL4N529x7IYJOGrLJJgLBPNVgD51zFfEYBzfJmMH2RUm7iegvDJaMCLISySw0zd6kcsqeJi7vtuybgY2NDPxDgiSOj4wX417PzB8AVg5bl1ZAAJ3LcVAqqtA1PDTU5ZU1QQYapNeBNxAHjnd2ojTZK1GJBIyY5Gd8P9gB880ppAKq8manNMZYsa4d8tkYf0SJUul2aqLIWJAwDGORpPmfjqkN4hMh85xAfPTZi6m4DdI0u2rHDMLaZ8eIsV16qA8wimSDnWi0VeG0SZ4ugbCdJAi3t1/uICTftiy06PJEkulBLV+h2xS/7SlmEY2xoN5ISi++3FNqsFPGa9QH6akGu2C7IXEUBCg3iGJx0uL+vULmVqk5OJIXdqiKVQ366hvhPlK2AM1zbh49x/ngibe08483WTL5uAY/fsKuBxQCpTc2368Gqhpd7QRtZFKpzikhyTWsR3nQIi6ExSstCeFbe8ehgo0PuTPZNHH5IHTc49snH6IZrSbR+F62Wu/D+4DlvMTK/ktG6LVQ3r3jSJC5MAQDV5Q9WK3RvsWMPvZrsaVW/Exz0GBgWP4W0adADg7MFSlnGDOJm6I4fCLHZIJCUww50L6iNmzvrdibrQT5jKACVgNquMZCfeZlf3m2BwUx9T6J45lAePpJ+QaMh+2voFqRiOLi98MLqOG6TW7z96sadzFVR9YU1xwM51jQDjnUlrXt0+msq29Jqt8LoCyQsG4r3RgS/tUJhximq11MDXsSXanpYM7jesjr8mAG4qjYN6z6c1Gl5N0dhcDF4HeEaIlNIgZ75FqtXZnLqvhHPyk6L2iR2ZT15nobZxLzOUad4a0OymUDUv7xuEBdEk5kmzZLDpbOxrKiyMpGSlbBhEoBMoA0u6ZKtBGQfCJ02s6Ri0WhLLM4XJCjGrpoEkTUuZ7YG39Zva19HGV0kkxeFYkG0lnZBO6jCggem5f+S2NQvXP/kUrWX1GeQFCq5PScvwJexLsbh0LKC2MGovkecoBKtNIK21V6ztvWL8lThJAl9' \
|
||||||
|
-H 'AppID: Minhthu2016' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H 'Origin: https://dotnet.elidev.info:8158' \
|
||||||
|
-H 'Connection: keep-alive' \
|
||||||
|
-H 'Referer: https://dotnet.elidev.info:8158/' \
|
||||||
|
-H 'Sec-Fetch-Dest: empty' \
|
||||||
|
-H 'Sec-Fetch-Mode: cors' \
|
||||||
|
-H 'Sec-Fetch-Site: same-site' \
|
||||||
|
-H 'Priority: u=0' \
|
||||||
|
--data-raw $'[{"TypeId":4,"ProductId":11,"StageId":3,"OrderId":null,"RecordDate":"2025-10-28T08:19:20.418Z","PassedQuantityWeight":0.5,"PassedQuantity":5,"IssuedQuantityWeight":0.1,"IssuedQuantity":1,"ResponsibleUserId":12043,"Description":"","ProductName":"Th\xe9p 435","ProductCode":"SCM435","StockPassedQuantityWeight":0,"StockPassedQuantity":0,"StockIssuedQuantity":0,"StockIssuedQuantityWeight":0,"ReceiverUserId":12120,"ActionTypeId":1,"WareHouseId":1,"ProductStageId":3,"IsConfirm":true}]'
|
||||||
@@ -35,8 +35,13 @@ class ProductsRemoteDataSourceImpl implements ProductsRemoteDataSource {
|
|||||||
@override
|
@override
|
||||||
Future<List<ProductModel>> getProducts(int warehouseId, String type) async {
|
Future<List<ProductModel>> getProducts(int warehouseId, String type) async {
|
||||||
try {
|
try {
|
||||||
// Make API call to get all products
|
// Choose endpoint based on operation type
|
||||||
final response = await apiClient.get('/portalProduct/getAllProduct');
|
final endpoint = type == 'export'
|
||||||
|
? ApiEndpoints.productsForExport(warehouseId)
|
||||||
|
: ApiEndpoints.products;
|
||||||
|
|
||||||
|
// Make API call to get products
|
||||||
|
final response = await apiClient.get(endpoint);
|
||||||
|
|
||||||
// Parse the API response using ApiResponse wrapper
|
// Parse the API response using ApiResponse wrapper
|
||||||
final apiResponse = ApiResponse.fromJson(
|
final apiResponse = ApiResponse.fromJson(
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ class ProductDetailPage extends ConsumerStatefulWidget {
|
|||||||
class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||||
late String _providerKey;
|
late String _providerKey;
|
||||||
|
|
||||||
|
// Text editing controllers for quantity fields
|
||||||
|
final TextEditingController _passedQuantityController = TextEditingController();
|
||||||
|
final TextEditingController _passedWeightController = TextEditingController();
|
||||||
|
final TextEditingController _issuedQuantityController = TextEditingController();
|
||||||
|
final TextEditingController _issuedWeightController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -53,6 +59,15 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_passedQuantityController.dispose();
|
||||||
|
_passedWeightController.dispose();
|
||||||
|
_issuedQuantityController.dispose();
|
||||||
|
_issuedWeightController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
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,
|
||||||
@@ -60,6 +75,13 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _clearControllers() {
|
||||||
|
_passedQuantityController.clear();
|
||||||
|
_passedWeightController.clear();
|
||||||
|
_issuedQuantityController.clear();
|
||||||
|
_issuedWeightController.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
@@ -333,16 +355,30 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
spacing: 16,
|
||||||
children: [
|
children: [
|
||||||
// Stage header
|
// Stage header
|
||||||
_buildStageHeader(stageToShow, theme),
|
_buildStageHeader(stageToShow, theme),
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Quantity information
|
|
||||||
_buildSectionCard(
|
_buildSectionCard(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
title: 'Quantities',
|
title: 'Stage Information',
|
||||||
icon: Icons.inventory_outlined,
|
icon: Icons.info_outlined,
|
||||||
|
children: [
|
||||||
|
_buildInfoRow('Product ID', '${stageToShow.productId}'),
|
||||||
|
if (stageToShow.productStageId != null)
|
||||||
|
_buildInfoRow('Stage ID', '${stageToShow.productStageId}'),
|
||||||
|
if (stageToShow.actionTypeId != null)
|
||||||
|
_buildInfoRow('Action Type ID', '${stageToShow.actionTypeId}'),
|
||||||
|
_buildInfoRow('Stage Name', stageToShow.displayName),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// Current Quantity information
|
||||||
|
_buildSectionCard(
|
||||||
|
theme: theme,
|
||||||
|
title: 'Current Quantities',
|
||||||
|
icon: Icons.info_outlined,
|
||||||
children: [
|
children: [
|
||||||
_buildInfoRow('Passed Quantity', '${stageToShow.passedQuantity}'),
|
_buildInfoRow('Passed Quantity', '${stageToShow.passedQuantity}'),
|
||||||
_buildInfoRow(
|
_buildInfoRow(
|
||||||
@@ -357,26 +393,59 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Stage information
|
// Add New Quantities section
|
||||||
_buildSectionCard(
|
_buildSectionCard(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
title: 'Stage Information',
|
title: 'Add New Quantities',
|
||||||
icon: Icons.info_outlined,
|
icon: Icons.add_circle_outline,
|
||||||
children: [
|
children: [
|
||||||
_buildInfoRow('Product ID', '${stageToShow.productId}'),
|
_buildTextField(
|
||||||
if (stageToShow.productStageId != null)
|
label: 'Passed Quantity',
|
||||||
_buildInfoRow('Stage ID', '${stageToShow.productStageId}'),
|
controller: _passedQuantityController,
|
||||||
if (stageToShow.actionTypeId != null)
|
keyboardType: TextInputType.number,
|
||||||
_buildInfoRow('Action Type ID', '${stageToShow.actionTypeId}'),
|
theme: theme,
|
||||||
_buildInfoRow('Stage Name', stageToShow.displayName),
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildTextField(
|
||||||
|
label: 'Passed Weight (kg)',
|
||||||
|
controller: _passedWeightController,
|
||||||
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
|
theme: theme,
|
||||||
|
),
|
||||||
|
const Divider(height: 24),
|
||||||
|
_buildTextField(
|
||||||
|
label: 'Issued Quantity',
|
||||||
|
controller: _issuedQuantityController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
theme: theme,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildTextField(
|
||||||
|
label: 'Issued Weight (kg)',
|
||||||
|
controller: _issuedWeightController,
|
||||||
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
|
theme: theme,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Status indicators
|
// Stage information
|
||||||
_buildStatusCards(stageToShow, theme),
|
|
||||||
|
|
||||||
|
|
||||||
|
// Add button
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: FilledButton.icon(
|
||||||
|
onPressed: () => _addNewQuantities(stageToShow),
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('Add Quantities'),
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -387,6 +456,48 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _addNewQuantities(ProductStageEntity stage) {
|
||||||
|
// Parse the values from text fields
|
||||||
|
final passedQuantity = int.tryParse(_passedQuantityController.text) ?? 0;
|
||||||
|
final passedWeight = double.tryParse(_passedWeightController.text) ?? 0.0;
|
||||||
|
final issuedQuantity = int.tryParse(_issuedQuantityController.text) ?? 0;
|
||||||
|
final issuedWeight = double.tryParse(_issuedWeightController.text) ?? 0.0;
|
||||||
|
|
||||||
|
// Validate that at least one field has a value
|
||||||
|
if (passedQuantity == 0 && passedWeight == 0.0 &&
|
||||||
|
issuedQuantity == 0 && issuedWeight == 0.0) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Please enter at least one quantity or weight value'),
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement API call to add new quantities
|
||||||
|
// For now, just show a success message
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
'Added: Passed Q=$passedQuantity, W=$passedWeight | Issued Q=$issuedQuantity, W=$issuedWeight',
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Log the values for debugging
|
||||||
|
debugPrint('Adding new quantities for stage ${stage.productStageId}:');
|
||||||
|
debugPrint(' Passed Quantity: $passedQuantity');
|
||||||
|
debugPrint(' Passed Weight: $passedWeight');
|
||||||
|
debugPrint(' Issued Quantity: $issuedQuantity');
|
||||||
|
debugPrint(' Issued Weight: $issuedWeight');
|
||||||
|
|
||||||
|
// Clear the text fields after successful add
|
||||||
|
_clearControllers();
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildStageHeader(ProductStageEntity stage, ThemeData theme) {
|
Widget _buildStageHeader(ProductStageEntity stage, ThemeData theme) {
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
@@ -501,6 +612,105 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildTextField({
|
||||||
|
required String label,
|
||||||
|
required TextEditingController controller,
|
||||||
|
required TextInputType keyboardType,
|
||||||
|
required ThemeData theme,
|
||||||
|
}) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: controller,
|
||||||
|
keyboardType: keyboardType,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: label,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: theme.colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: theme.colorScheme.surface,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: () => _incrementValue(controller, 0.1),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
),
|
||||||
|
child: const Text('+0.1'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: () => _incrementValue(controller, 0.5),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
),
|
||||||
|
child: const Text('+0.5'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: () => _incrementValue(controller, 1),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
),
|
||||||
|
child: const Text('+1'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: () => controller.clear(),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
foregroundColor: theme.colorScheme.error,
|
||||||
|
),
|
||||||
|
child: const Text('C'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _incrementValue(TextEditingController controller, double increment) {
|
||||||
|
final currentValue = double.tryParse(controller.text) ?? 0.0;
|
||||||
|
final newValue = currentValue + increment;
|
||||||
|
|
||||||
|
// Format the value based on whether it's a whole number or has decimals
|
||||||
|
if (newValue == newValue.toInt()) {
|
||||||
|
controller.text = newValue.toInt().toString();
|
||||||
|
} else {
|
||||||
|
controller.text = newValue.toStringAsFixed(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildStatusCards(ProductStageEntity stage, ThemeData theme) {
|
Widget _buildStatusCards(ProductStageEntity stage, ThemeData theme) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import '../../../../core/router/app_router.dart';
|
|||||||
import '../widgets/product_list_item.dart';
|
import '../widgets/product_list_item.dart';
|
||||||
|
|
||||||
/// Products list page
|
/// Products list page
|
||||||
/// Displays products for a specific warehouse and operation type
|
/// Displays products for a specific warehouse with import/export tabs
|
||||||
class ProductsPage extends ConsumerStatefulWidget {
|
class ProductsPage extends ConsumerStatefulWidget {
|
||||||
final int warehouseId;
|
final int warehouseId;
|
||||||
final String warehouseName;
|
final String warehouseName;
|
||||||
@@ -24,20 +24,61 @@ class ProductsPage extends ConsumerStatefulWidget {
|
|||||||
ConsumerState<ProductsPage> createState() => _ProductsPageState();
|
ConsumerState<ProductsPage> createState() => _ProductsPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ProductsPageState extends ConsumerState<ProductsPage> {
|
class _ProductsPageState extends ConsumerState<ProductsPage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late TabController _tabController;
|
||||||
|
String _currentOperationType = 'import';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
// Initialize tab controller
|
||||||
|
_tabController = TabController(
|
||||||
|
length: 2,
|
||||||
|
vsync: this,
|
||||||
|
initialIndex: widget.operationType == 'export' ? 1 : 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
_currentOperationType = widget.operationType;
|
||||||
|
|
||||||
|
// Listen to tab changes
|
||||||
|
_tabController.addListener(() {
|
||||||
|
if (_tabController.indexIsChanging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final newOperationType = _tabController.index == 0 ? 'import' : 'export';
|
||||||
|
if (_currentOperationType != newOperationType) {
|
||||||
|
setState(() {
|
||||||
|
_currentOperationType = newOperationType;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load products for new operation type
|
||||||
|
ref.read(productsProvider.notifier).loadProducts(
|
||||||
|
widget.warehouseId,
|
||||||
|
widget.warehouseName,
|
||||||
|
_currentOperationType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Load products when page is initialized
|
// Load products when page is initialized
|
||||||
Future.microtask(() {
|
Future.microtask(() {
|
||||||
ref.read(productsProvider.notifier).loadProducts(
|
ref.read(productsProvider.notifier).loadProducts(
|
||||||
widget.warehouseId,
|
widget.warehouseId,
|
||||||
widget.warehouseName,
|
widget.warehouseName,
|
||||||
widget.operationType,
|
_currentOperationType,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tabController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onRefresh() async {
|
Future<void> _onRefresh() async {
|
||||||
await ref.read(productsProvider.notifier).refreshProducts();
|
await ref.read(productsProvider.notifier).refreshProducts();
|
||||||
}
|
}
|
||||||
@@ -194,7 +235,7 @@ class _ProductsPageState extends ConsumerState<ProductsPage> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Products (${_getOperationTypeDisplay()})',
|
'Products',
|
||||||
style: textTheme.titleMedium,
|
style: textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
@@ -212,6 +253,19 @@ class _ProductsPageState extends ConsumerState<ProductsPage> {
|
|||||||
tooltip: 'Refresh',
|
tooltip: 'Refresh',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
bottom: TabBar(
|
||||||
|
controller: _tabController,
|
||||||
|
tabs: const [
|
||||||
|
Tab(
|
||||||
|
icon: Icon(Icons.arrow_downward),
|
||||||
|
text: 'Import',
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
icon: Icon(Icons.arrow_upward),
|
||||||
|
text: 'Export',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
body: _buildBody(
|
body: _buildBody(
|
||||||
isLoading: isLoading,
|
isLoading: isLoading,
|
||||||
@@ -253,10 +307,10 @@ class _ProductsPageState extends ConsumerState<ProductsPage> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
widget.operationType == 'import'
|
_currentOperationType == 'import'
|
||||||
? Icons.arrow_downward
|
? Icons.arrow_downward
|
||||||
: Icons.arrow_upward,
|
: Icons.arrow_upward,
|
||||||
color: widget.operationType == 'import'
|
color: _currentOperationType == 'import'
|
||||||
? Colors.green
|
? Colors.green
|
||||||
: Colors.orange,
|
: Colors.orange,
|
||||||
),
|
),
|
||||||
@@ -266,7 +320,9 @@ class _ProductsPageState extends ConsumerState<ProductsPage> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
_getOperationTypeDisplay(),
|
_currentOperationType == 'import'
|
||||||
|
? 'Import Products'
|
||||||
|
: 'Export Products',
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@@ -417,11 +473,4 @@ class _ProductsPageState extends ConsumerState<ProductsPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get display text for operation type
|
|
||||||
String _getOperationTypeDisplay() {
|
|
||||||
return widget.operationType == 'import'
|
|
||||||
? 'Import Products'
|
|
||||||
: 'Export Products';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,11 +170,18 @@ class _WarehouseSelectionPageState
|
|||||||
return WarehouseCard(
|
return WarehouseCard(
|
||||||
warehouse: warehouse,
|
warehouse: warehouse,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Select warehouse and navigate to operations
|
// Select warehouse and navigate directly to products page
|
||||||
ref.read(warehouseProvider.notifier).selectWarehouse(warehouse);
|
ref.read(warehouseProvider.notifier).selectWarehouse(warehouse);
|
||||||
|
|
||||||
// Navigate to operations page
|
// Navigate to products page with warehouse data
|
||||||
context.push('/operations', extra: warehouse);
|
context.push(
|
||||||
|
'/products',
|
||||||
|
extra: {
|
||||||
|
'warehouse': warehouse,
|
||||||
|
'warehouseName': warehouse.name,
|
||||||
|
'operationType': 'import', // Default to import
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user