This commit is contained in:
Phuoc Nguyen
2025-10-29 15:52:24 +07:00
parent 2905668358
commit cb4df363ab
6 changed files with 707 additions and 137 deletions

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/di/providers.dart';
import '../../../../core/services/print_service.dart';
import '../../../users/domain/entities/user_entity.dart';
import '../../data/models/create_product_warehouse_request.dart';
import '../../domain/entities/product_stage_entity.dart';
@@ -118,21 +119,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
return Scaffold(
appBar: AppBar(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
operationTitle,
style: textTheme.titleMedium,
),
Text(
productName,
style: textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
title: Text('${operationTitle} ${productName}'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
@@ -149,6 +136,11 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
selectedIndex: selectedIndex,
theme: theme,
),
bottomNavigationBar: _buildBottomActionBar(
selectedStage: selectedStage,
stages: stages,
theme: theme,
),
);
}
@@ -277,7 +269,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
if (displayStages.isNotEmpty)
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHighest,
border: Border(
@@ -289,39 +281,6 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
widget.stageId != null
? 'Công đoạn'
: 'Công đoạn (${displayStages.length})',
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
if (widget.stageId != null) ...[
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: theme.colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'ID: ${widget.stageId}',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
),
),
),
],
],
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
@@ -376,43 +335,24 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 16,
spacing: 8,
children: [
// Stage header
_buildStageHeader(stageToShow, theme),
_buildSectionCard(
theme: theme,
title: 'Thông tin công đoạn',
icon: Icons.info_outlined,
children: [
_buildInfoRow('Mã sản phẩm', '${stageToShow.productId}'),
if (stageToShow.productStageId != null)
_buildInfoRow('Mã công đoạn', '${stageToShow.productStageId}'),
if (stageToShow.actionTypeId != null)
_buildInfoRow('Mã loại thao tác', '${stageToShow.actionTypeId}'),
_buildInfoRow('Tên công đoạn', stageToShow.displayName),
],
),
// Current Quantity information
_buildSectionCard(
theme: theme,
title: 'Số lượng hiện tại',
icon: Icons.info_outlined,
children: [
_buildInfoRow('Số lượng đạt', '${stageToShow.passedQuantity}'),
_buildInfoRow(
'Khối lượng đạt',
'${stageToShow.passedQuantityWeight.toStringAsFixed(2)} kg',
),
_buildInfoRow('Số lượng lỗi', '${stageToShow.issuedQuantity}'),
_buildInfoRow(
'Khối lượng lỗi',
'${stageToShow.issuedQuantityWeight.toStringAsFixed(2)} kg',
),
],
),
// _buildStageHeader(stageToShow, theme),
//
// _buildSectionCard(
// theme: theme,
// title: 'Thông tin công đoạn',
// icon: Icons.info_outlined,
// children: [
// _buildInfoRow('Mã sản phẩm', '${stageToShow.productId}'),
// if (stageToShow.productStageId != null)
// _buildInfoRow('Mã công đoạn', '${stageToShow.productStageId}'),
// if (stageToShow.actionTypeId != null)
// _buildInfoRow('Mã loại thao tác', '${stageToShow.actionTypeId}'),
// _buildInfoRow('Tên công đoạn', stageToShow.displayName),
// ],
// ),
// Add New Quantities section
_buildSectionCard(
@@ -462,6 +402,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
},
theme: theme,
),
const SizedBox(height: 8),
// All Employees Dropdown
_buildUserDropdown(
label: 'Nhân viên',
@@ -476,30 +417,21 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
),
]),
// Action buttons
Row(
spacing: 12,
// Current Quantity information
_buildSectionCard(
theme: theme,
title: 'Số lượng hiện tại',
icon: Icons.info_outlined,
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () => _printQuantities(stageToShow),
icon: const Icon(Icons.print),
label: const Text('In'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
_buildInfoRow('Số lượng đạt', '${stageToShow.passedQuantity}'),
_buildInfoRow(
'Khối lượng đạt',
'${stageToShow.passedQuantityWeight.toStringAsFixed(2)} kg',
),
Expanded(
child: FilledButton.icon(
onPressed: () => _addNewQuantities(stageToShow),
icon: const Icon(Icons.save),
label: const Text('Lưu'),
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
_buildInfoRow('Số lượng lỗi', '${stageToShow.issuedQuantity}'),
_buildInfoRow(
'Khối lượng lỗi',
'${stageToShow.issuedQuantityWeight.toStringAsFixed(2)} kg',
),
],
),
@@ -513,14 +445,55 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
);
}
void _printQuantities(ProductStageEntity stage) {
// TODO: Implement print functionality
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Tính năng in đang phát triển'),
duration: Duration(seconds: 2),
),
);
Future<void> _printQuantities(ProductStageEntity stage) async {
// Get the current quantity values (entered by user or use current values)
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;
// Use entered values if available, otherwise use current stock values
final finalPassedPcs = passedQuantity > 0 ? passedQuantity : stage.passedQuantity;
final finalPassedKg = passedWeight > 0.0 ? passedWeight : stage.passedQuantityWeight;
final finalIssuedPcs = issuedQuantity > 0 ? issuedQuantity : stage.issuedQuantity;
final finalIssuedKg = issuedWeight > 0.0 ? issuedWeight : stage.issuedQuantityWeight;
// Get responsible user name
final responsibleName = _selectedWarehouseUser != null
? '${_selectedWarehouseUser!.name} ${_selectedWarehouseUser!.firstName}'
: null;
// Generate barcode data (using product code or product ID)
final barcodeData = stage.productCode.isNotEmpty
? stage.productCode
: 'P${stage.productId}';
try {
await PrintService.printWarehouseExport(
context: context,
warehouseName: widget.warehouseName,
productId: stage.productId,
productCode: stage.productCode,
productName: stage.productName,
stageName: stage.displayName,
passedKg: finalPassedKg,
passedPcs: finalPassedPcs,
issuedKg: finalIssuedKg,
issuedPcs: finalIssuedPcs,
responsibleName: responsibleName,
barcodeData: barcodeData,
);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error printing: ${e.toString()}'),
backgroundColor: Colors.red,
duration: const Duration(seconds: 3),
),
);
}
}
}
Future<void> _addNewQuantities(ProductStageEntity stage) async {
@@ -708,28 +681,11 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
}) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 12,
spacing: 4,
children: [
Row(
children: [
Icon(
icon,
size: 20,
color: theme.colorScheme.primary,
),
const SizedBox(width: 8),
Text(
title,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 16),
...children,
],
),
@@ -921,4 +877,69 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
}
}
Widget? _buildBottomActionBar({
required ProductStageEntity? selectedStage,
required List<ProductStageEntity> stages,
required ThemeData theme,
}) {
// Determine which stage to show
// When stageId is provided, use the filtered stage
final displayStages = widget.stageId != null
? stages.where((stage) => stage.productStageId == widget.stageId).toList()
: stages;
final stageToShow = widget.stageId != null && displayStages.isNotEmpty
? displayStages.first
: selectedStage;
// Don't show action bar if there's no stage to work with
if (stageToShow == null) {
return null;
}
return Container(
decoration: BoxDecoration(
color: theme.colorScheme.surface,
boxShadow: [
BoxShadow(
color: theme.colorScheme.shadow.withValues(alpha: 0.1),
blurRadius: 8,
offset: const Offset(0, -2),
),
],
),
child: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
spacing: 12,
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () => _printQuantities(stageToShow),
icon: const Icon(Icons.print),
label: const Text('In'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
),
Expanded(
child: FilledButton.icon(
onPressed: () => _addNewQuantities(stageToShow),
icon: const Icon(Icons.save),
label: const Text('Lưu'),
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
),
],
),
),
),
);
}
}