update package detal list and update cargo type input in carton section

This commit is contained in:
tzw
2025-03-25 17:38:51 +06:30
parent 3d4bc43de4
commit 2bd75f0333
14 changed files with 597 additions and 812 deletions

View File

@@ -1,3 +1,5 @@
import 'package:fcs/pages/main/util.dart';
class CargoType { class CargoType {
String? id; String? id;
String? name; String? name;
@@ -96,7 +98,7 @@ class CargoType {
Map<String, dynamic> toMapForCarton() { Map<String, dynamic> toMapForCarton() {
return { return {
"id": id, "id": id,
'weight': weight, 'weight': double.tryParse(twoDecimalFormatted(weight)) ?? 0.00,
"mix_cargo_type_ids": mixCargoes.map((e) => e.id).toList() "mix_cargo_type_ids": mixCargoes.map((e) => e.id).toList()
}; };
} }
@@ -109,6 +111,22 @@ class CargoType {
return CargoType.fromMap(toMap(), id!); return CargoType.fromMap(toMap(), id!);
} }
CargoType cloneForCarton() {
return CargoType(
id: id,
name: name,
weight: weight,
isDefault: isDefault,
displayIndex: displayIndex,
isMixCargo: isMixCargo,
mixCargoIds: List.from(mixCargoIds),
mixCargoes: List.from(mixCargoes));
}
CargoType cloneForSurchage() {
return CargoType(id: id, name: name, qty: qty);
}
@override @override
bool operator ==(Object other) => other is CargoType && other.id == id; bool operator ==(Object other) => other is CargoType && other.id == id;

View File

@@ -52,9 +52,9 @@ class _CargoWidgetState extends State<CargoWidget> {
List<TextEditingController> surchargeControllers = []; List<TextEditingController> surchargeControllers = [];
bool get hasValueTotalWeight => bool get hasValueTotalWeight =>
totalCtl.text.isNotEmpty && totalCtl.text != '0'; totalCtl.text.isNotEmpty && totalCtl.text != '0.00';
bool get hasValueCargoes => bool get hasValueCargoes =>
_cargoTypes.isNotEmpty && _cargoTypes.every((e) => e.weight != 0); _cargoTypes.isNotEmpty && _cargoTypes.every((e) => e.weight != 0.00);
double get actualTotalWeight => double get actualTotalWeight =>
_cargoTypes.fold(0, (sum, value) => sum + value.weight); _cargoTypes.fold(0, (sum, value) => sum + value.weight);
@@ -72,11 +72,12 @@ class _CargoWidgetState extends State<CargoWidget> {
_cargoTypes = List.from(widget.cargoTypes); _cargoTypes = List.from(widget.cargoTypes);
for (var e in _cargoTypes) { for (var e in _cargoTypes) {
var editor = TextEditingController(); var editor = TextEditingController();
editor.text = removeTrailingZeros(e.weight); editor.text = twoDecimalFormatted(
double.tryParse(removeTrailingZeros(e.weight)) ?? 0);
editor.addListener(inputChangeListener); editor.addListener(inputChangeListener);
cargoTypeControllers.add(editor); cargoTypeControllers.add(editor);
} }
onUpdated(); _onPopulate();
} else { } else {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_openCargoTypeSelection(); _openCargoTypeSelection();
@@ -108,11 +109,14 @@ class _CargoWidgetState extends State<CargoWidget> {
for (var e in _cargoTypes) { for (var e in _cargoTypes) {
var editor = TextEditingController(); var editor = TextEditingController();
editor.text = '0'; editor.text = '0.00';
editor.addListener(inputChangeListener); editor.addListener(inputChangeListener);
cargoTypeControllers.add(editor); cargoTypeControllers.add(editor);
} }
totalCtl.text = twoDecimalFormatted(
double.tryParse(removeTrailingZeros(actualTotalWeight)) ?? 0);
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
} }
@@ -133,9 +137,10 @@ class _CargoWidgetState extends State<CargoWidget> {
return emptyFields; return emptyFields;
} }
void onUpdated() { void _onPopulate() {
if (!hasValueTotalWeight && hasValueCargoes) { if (!hasValueTotalWeight && hasValueCargoes) {
totalCtl.text = removeTrailingZeros(actualTotalWeight); totalCtl.text = twoDecimalFormatted(
double.tryParse(removeTrailingZeros(actualTotalWeight)) ?? 0);
error = null; error = null;
} else { } else {
// auto populate remaining value // auto populate remaining value
@@ -155,8 +160,8 @@ class _CargoWidgetState extends State<CargoWidget> {
_cargoTypes.asMap().entries.forEach((e) { _cargoTypes.asMap().entries.forEach((e) {
if (e.value.weight == 0) { if (e.value.weight == 0) {
e.value.weight = remainingWeight; e.value.weight = remainingWeight;
cargoTypeControllers[e.key].text = cargoTypeControllers[e.key].text = twoDecimalFormatted(
removeTrailingZeros(e.value.weight); double.tryParse(removeTrailingZeros(e.value.weight)) ?? 0);
} }
}); });
} }
@@ -168,6 +173,18 @@ class _CargoWidgetState extends State<CargoWidget> {
} }
} }
void _onCheckTotalWeight(String value) {
double totalWeight = double.tryParse(value) ?? 0;
if (totalWeight != actualTotalWeight) {
error = "Invalid total weight";
} else {
error = null;
}
if (mounted) {
setState(() {});
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final senderBox = userDisplayBox(context, final senderBox = userDisplayBox(context,
@@ -208,19 +225,30 @@ class _CargoWidgetState extends State<CargoWidget> {
CargoTypeAddition(cargoTypes: _cargoTypes))); CargoTypeAddition(cargoTypes: _cargoTypes)));
if (cargoType == null) return; if (cargoType == null) return;
if (!cargoType.isMixCargo) { // add cargo type
_cargoTypes.add(cargoType);
}
_cargoTypes.sort((a, b) => (a == b ? 0 : (a.isMixCargo ? 1 : -1)));
if (cargoType.isMixCargo) { if (cargoType.isMixCargo) {
_cargoTypes.add(cargoType); int lastTrueIndex =
_cargoTypes.lastIndexWhere((e) => e.isMixCargo);
if (lastTrueIndex != -1) {
_cargoTypes.insert(lastTrueIndex + 1, cargoType);
} else {
_cargoTypes.add(cargoType);
}
} else {
int lastFalseIndex =
_cargoTypes.lastIndexWhere((e) => !e.isMixCargo);
if (lastFalseIndex != -1) {
_cargoTypes.insert(lastFalseIndex + 1, cargoType);
} else {
_cargoTypes.insert(0, cargoType);
}
} }
cargoTypeControllers.clear(); cargoTypeControllers.clear();
for (var e in _cargoTypes) { for (var e in _cargoTypes) {
var editor = TextEditingController(); var editor = TextEditingController();
editor.text = removeTrailingZeros(e.weight); editor.text = twoDecimalFormatted(
double.tryParse(removeTrailingZeros(e.weight)) ?? 0);
editor.addListener(inputChangeListener); editor.addListener(inputChangeListener);
cargoTypeControllers.add(editor); cargoTypeControllers.add(editor);
} }
@@ -230,177 +258,53 @@ class _CargoWidgetState extends State<CargoWidget> {
}), }),
); );
final cargosBox = Padding( final totalWeightBox = Padding(
padding: const EdgeInsets.only(top: 5), padding: const EdgeInsets.only(top: 5),
child: Wrap( child: Row(
alignment: WrapAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.end,
runSpacing: 25, children: [
children: _cargoTypes.asMap().entries.map((e) { SizedBox(
var key = e.key;
var c = e.value;
return SizedBox(
width: MediaQuery.of(context).size.width / 2.3, width: MediaQuery.of(context).size.width / 2.3,
child: Column( child: Row(
children: [ children: [
Row( InkResponse(
children: [ radius: 25,
InkResponse( onTap: () {
radius: 25,
onTap: () {
double totalWeight =
double.tryParse(totalCtl.text) ?? 0;
if (actualTotalWeight > totalWeight) {
error = "Exceed total weight";
} else {
double result = totalWeight - c.weight;
totalCtl.text = removeTrailingZeros(result);
}
_cargoTypes.removeAt(key);
cargoTypeControllers.removeAt(key);
if (mounted) {
setState(() {});
}
},
child: Icon(Feather.minus_circle, color: labelColor)),
const SizedBox(width: 10),
Flexible(
child: inputTextFieldWidget(context,
lableText: c.name ?? "",
controller: cargoTypeControllers[key],
onFieldSubmitted: (value) {
_cargoTypes[e.key].weight =
double.tryParse(value) ?? 0;
onUpdated();
},
suffixIcon: InkResponse(
radius: 23,
onTap: () {
double totalWeight =
(double.tryParse(totalCtl.text) ?? 0);
var list = _cargoTypes
.where((e) => e.id != c.id)
.toList();
double resetValue = totalWeight -
(list.fold(0,
(sum, value) => sum + value.weight));
setState(() {
e.value.weight = resetValue;
cargoTypeControllers[e.key].text =
removeTrailingZeros(resetValue);
});
onUpdated();
},
child: Icon(Ionicons.md_refresh_circle,
color: labelColor, size: 22))),
),
c.isMixCargo
? InkResponse(
radius: 23,
onTap: () async {
List<CargoType>? cargoes = await showDialog(
context: context,
builder: (_) => MixCargoTypeAdditionDialog(
cargoTypes: c.mixCargoes));
if (cargoes == null) return;
setState(() {
c.mixCargoes = List.from(cargoes);
});
},
child: Icon(Icons.add_circle,
color: labelColor, size: 22))
: const SizedBox()
],
),
c.mixCargoes.isEmpty
? const SizedBox()
: Padding(
padding: const EdgeInsets.only(top: 5),
child: Column(
children: c.mixCargoes.map((e) {
return Padding(
padding: const EdgeInsets.only(top: 12),
child: Row(
children: [
const SizedBox(width: 25),
InkResponse(
radius: 23,
onTap: () {
setState(() {
c.mixCargoes.remove(e);
});
},
child: Icon(Feather.minus_circle,
color: labelColor, size: 20)),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(e.name ?? ""),
),
],
),
);
}).toList()),
)
],
),
);
}).toList()),
);
final totalWeightBox = Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
width: MediaQuery.of(context).size.width / 2.3,
child: Row(
children: [
InkResponse(
radius: 25,
onTap: () {
setState(() {
totalCtl.clear();
});
},
child: Icon(MaterialIcons.clear, color: labelColor)),
const SizedBox(width: 10),
Flexible(
child: inputTextFieldWidget(context,
lableText: "Total",
controller: totalCtl, onFieldSubmitted: (neValue) {
if (hasValueCargoes) {
double totalWeight = double.tryParse(neValue) ?? 0;
if (totalWeight < actualTotalWeight) {
setState(() { setState(() {
error = "Invalid total weight"; totalCtl.clear();
}); });
_onCheckTotalWeight(totalCtl.text);
},
child: Icon(MaterialIcons.clear, color: labelColor)),
const SizedBox(width: 10),
Flexible(
child: inputTextFieldWidget(context,
lableText: "Total",
controller: totalCtl, onFieldSubmitted: (newValue) {
if (hasValueCargoes) {
_onCheckTotalWeight(newValue);
} else { } else {
setState(() { _onPopulate();
error = null;
});
} }
} else { },
onUpdated(); suffixIcon: InkResponse(
} radius: 23,
}, onTap: () {
suffixIcon: InkResponse( setState(() {
radius: 23, totalCtl.text = twoDecimalFormatted(
onTap: () { double.tryParse(removeTrailingZeros(
setState(() { actualTotalWeight)) ??
totalCtl.text = 0);
removeTrailingZeros(actualTotalWeight); error = null;
error = null; });
}); },
}, child: Icon(Ionicons.md_refresh_circle,
child: Icon(Ionicons.md_refresh_circle, color: labelColor, size: 22))),
color: labelColor, size: 22))), ),
), ],
], )),
)), ],
], ),
); );
final subchargeItemTitleBox = LocalTitle( final subchargeItemTitleBox = LocalTitle(
@@ -481,17 +385,161 @@ class _CargoWidgetState extends State<CargoWidget> {
context, "Error", "Please insert surcharge item quantity"); context, "Error", "Please insert surcharge item quantity");
return; return;
} }
if (error != null) {
showMsgDialog(
context, "Error", "Please add the right cargo type weight");
return;
}
widget.onContinue!(_cargoTypes, _surchareItems); widget.onContinue!(_cargoTypes, _surchareItems);
} }
}, },
); );
final previousBtn = PreviousButton(onTap: () { final previousBtn = PreviousButton(onTap: () {
if (error != null) {
showMsgDialog(
context, "Error", "Please add the right cargo type weight");
return;
}
if (widget.onPrevious != null) { if (widget.onPrevious != null) {
widget.onPrevious!(_cargoTypes, _surchareItems); widget.onPrevious!(_cargoTypes, _surchareItems);
} }
}); });
Widget cargoesWidget(List<CargoType> items) {
List<Widget> widgets = [];
for (int i = 0; i < items.length; i++) {
var key = i;
var c = items[i];
if (i > 0 && (!items[i - 1].isMixCargo && items[i].isMixCargo)) {
widgets.add(Padding(
padding: const EdgeInsets.symmetric(horizontal: 70),
child: Divider(color: Colors.grey.shade300)));
}
widgets.add(SizedBox(
width: MediaQuery.of(context).size.width / 2.3,
child: Column(
children: [
Row(
children: [
InkResponse(
radius: 25,
onTap: () {
_cargoTypes.removeAt(key);
cargoTypeControllers.removeAt(key);
_onCheckTotalWeight(totalCtl.text);
if (mounted) {
setState(() {});
}
},
child: Icon(Feather.minus_circle, color: labelColor)),
const SizedBox(width: 10),
Flexible(
child: inputTextFieldWidget(context,
lableText: c.name ?? "",
controller: cargoTypeControllers[key],
onFieldSubmitted: (value) {
_cargoTypes[key].weight = double.tryParse(value) ?? 0;
_onPopulate();
},
suffixIcon: InkResponse(
radius: 23,
onTap: () {
double totalWeight =
(double.tryParse(totalCtl.text) ?? 0);
var list = _cargoTypes
.where((e) => e.id != c.id)
.toList();
double sum = (list.fold(
0, (sum, value) => sum + value.weight));
if (sum > totalWeight) {
error = "Exceed total weight";
} else {
error = null;
double resetValue = totalWeight - sum;
setState(() {
c.weight = resetValue;
cargoTypeControllers[key].text =
twoDecimalFormatted(double.tryParse(
removeTrailingZeros(
resetValue)) ??
0);
});
_onPopulate();
}
},
child: Icon(Ionicons.md_refresh_circle,
color: labelColor, size: 22))),
),
c.isMixCargo
? InkResponse(
radius: 23,
onTap: () async {
List<CargoType>? cargoes = await showDialog(
context: context,
builder: (_) => MixCargoTypeAdditionDialog(
cargoTypes: c.mixCargoes));
if (cargoes == null) return;
setState(() {
c.mixCargoes = List.from(cargoes);
});
},
child: Icon(Icons.add_circle,
color: labelColor, size: 22))
: const SizedBox()
],
),
c.mixCargoes.isEmpty
? const SizedBox()
: Padding(
padding: const EdgeInsets.only(top: 5),
child: Column(
children: c.mixCargoes.map((e) {
return Padding(
padding: const EdgeInsets.only(top: 12),
child: Row(
children: [
const SizedBox(width: 25),
InkResponse(
radius: 23,
onTap: () {
setState(() {
c.mixCargoes.remove(e);
});
},
child: Icon(Feather.minus_circle,
color: labelColor, size: 20)),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(e.name ?? ""),
),
],
),
);
}).toList()),
)
],
),
));
}
return Padding(
padding: const EdgeInsets.only(top: 5),
child: Wrap(
alignment: WrapAlignment.spaceBetween,
runSpacing: 25,
children: widgets),
);
}
return Column( return Column(
children: [ children: [
Expanded( Expanded(
@@ -502,9 +550,13 @@ class _CargoWidgetState extends State<CargoWidget> {
const SizedBox(height: 8), const SizedBox(height: 8),
userRow, userRow,
cargoTitle, cargoTitle,
cargosBox, cargoesWidget(_cargoTypes),
const SizedBox(height: 15), const SizedBox(height: 15),
Divider(), _cargoTypes.isNotEmpty
? Divider(
color: Colors.grey.shade300,
)
: const SizedBox(),
const SizedBox(height: 5), const SizedBox(height: 5),
error != null error != null
? Text( ? Text(
@@ -553,7 +605,11 @@ class _CargoWidgetState extends State<CargoWidget> {
onFieldSubmitted: onFieldSubmitted, onFieldSubmitted: onFieldSubmitted,
readOnly: readOnly, readOnly: readOnly,
decoration: InputDecoration( decoration: InputDecoration(
suffixIcon: suffixIcon, suffixIcon: Padding(
padding: const EdgeInsets.only(right: 8),
child: suffixIcon,
),
suffixIconConstraints: BoxConstraints(minWidth: 0, minHeight: 0),
contentPadding: EdgeInsets.all(0), contentPadding: EdgeInsets.all(0),
labelText: lableText, labelText: lableText,
labelStyle: newLabelStyle(color: Colors.black54, fontSize: 17), labelStyle: newLabelStyle(color: Colors.black54, fontSize: 17),

View File

@@ -7,7 +7,6 @@ import 'package:fcs/domain/entities/package.dart';
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/carton/carton_image_upload_editor.dart'; import 'package:fcs/pages/carton/carton_image_upload_editor.dart';
import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/main/util.dart';
import 'package:fcs/pages/package/model/package_model.dart';
import 'package:fcs/pages/widgets/display_text.dart'; import 'package:fcs/pages/widgets/display_text.dart';
import 'package:fcs/pages/widgets/local_app_bar.dart'; import 'package:fcs/pages/widgets/local_app_bar.dart';
import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_text.dart';
@@ -28,6 +27,7 @@ import 'carton_package_editor.dart';
import 'mix_carton/mix_carton_editor.dart'; import 'mix_carton/mix_carton_editor.dart';
import 'mix_carton_detail_list.dart'; import 'mix_carton_detail_list.dart';
import 'model/carton_model.dart'; import 'model/carton_model.dart';
import 'model/package_selection_model.dart';
import 'package_detail_list.dart'; import 'package_detail_list.dart';
import 'print_qr_code_page.dart'; import 'print_qr_code_page.dart';
@@ -48,10 +48,11 @@ class _CartonInfoState extends State<CartonInfo> {
List<CargoType> _cargoTypes = []; List<CargoType> _cargoTypes = [];
List<CargoType> _surchareItems = []; List<CargoType> _surchareItems = [];
List<Carton> _mixCartons = []; List<Carton> _mixCartons = [];
List<Package> _packages = [];
double totalWeight = 0.0; double _totalWeight = 0.0;
double totalSurchargeCount = 0.0; int _packageCount = 0;
CartonSize? standardSize;
CartonSize? _standardSize;
FcsShipment? _shipment; FcsShipment? _shipment;
@override @override
@@ -77,7 +78,7 @@ class _CartonInfoState extends State<CartonInfo> {
bool isStandartSize = sameLength && sameWidth && sameHeight; bool isStandartSize = sameLength && sameWidth && sameHeight;
if (isStandartSize) { if (isStandartSize) {
_carton.cartonSizeType = standardCarton; _carton.cartonSizeType = standardCarton;
standardSize = cartonSizes.firstWhere((size) => _standardSize = cartonSizes.firstWhere((size) =>
size.length == _carton.length && size.length == _carton.length &&
size.width == _carton.width && size.width == _carton.width &&
size.height == _carton.height); size.height == _carton.height);
@@ -89,32 +90,11 @@ class _CartonInfoState extends State<CartonInfo> {
_carton.cartonSizeType = customCarton; _carton.cartonSizeType = customCarton;
} }
if (widget.carton.fcsShipmentID != null && await Future.wait(
widget.carton.fcsShipmentID != '') { [_loadFcsShipment(), _loadPackageCount(), _loadMixCargoes()]);
var s = await context
.read<FcsShipmentModel>()
.getFcsShipment(widget.carton.fcsShipmentID!);
_shipment = s;
}
if (_carton.cartonType == carton_from_packages) { _totalWeight =
_packages = await context
.read<PackageModel>()
.getPackagesByIds(_carton.packageIDs);
}
if (_carton.cartonType == mix_carton) {
_mixCartons = await context
.read<CartonModel>()
.getCartonsByIds(_carton.cartonIDs);
_cargoTypes.sort((a, b) => a.name!.compareTo(b.name!));
_surchareItems.sort((a, b) => a.name!.compareTo(b.name!));
}
totalWeight =
_carton.cargoTypes.fold(0, (sum, value) => sum + value.weight); _carton.cargoTypes.fold(0, (sum, value) => sum + value.weight);
totalSurchargeCount =
_surchareItems.fold(0, (sum, value) => sum + value.qty);
} finally { } finally {
_isLoading = false; _isLoading = false;
} }
@@ -124,12 +104,50 @@ class _CartonInfoState extends State<CartonInfo> {
} }
} }
Future<void> _loadFcsShipment() async {
if (widget.carton.fcsShipmentID != null &&
widget.carton.fcsShipmentID != '') {
var s = await context
.read<FcsShipmentModel>()
.getFcsShipment(widget.carton.fcsShipmentID!);
_shipment = s;
if (mounted) {
setState(() {});
}
}
}
Future<void> _loadPackageCount() async {
if (_carton.cartonType == carton_from_packages) {
int? count = await context.read<PackageSelectionModel>().getPackageCount(
senderId: widget.carton.senderID ?? "",
consigneeId: widget.carton.consigneeID ?? "",
shipmentId: widget.carton.fcsShipmentID ?? "");
_packageCount = count ?? 0;
if (mounted) {
setState(() {});
}
}
}
Future<void> _loadMixCargoes() async {
if (_carton.cartonType == mix_carton) {
_mixCartons =
await context.read<CartonModel>().getCartonsByIds(_carton.cartonIDs);
_cargoTypes.sort((a, b) => a.name!.compareTo(b.name!));
_surchareItems.sort((a, b) => a.name!.compareTo(b.name!));
if (mounted) {
setState(() {});
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var fromPackage = _carton.cartonType == carton_from_packages; var fromPackage = _carton.cartonType == carton_from_packages;
String? boxDimension = _carton.cartonSizeType == standardCarton String? boxDimension = _carton.cartonSizeType == standardCarton
? "${standardSize?.name} - ${standardSize?.length.toInt()}”x${standardSize?.width.toInt()}”x${standardSize?.height.toInt()}" ? "${_standardSize?.name} - ${_standardSize?.length.toInt()}”x${_standardSize?.width.toInt()}”x${_standardSize?.height.toInt()}"
: _carton.cartonSizeType == customCarton : _carton.cartonSizeType == customCarton
? "${_carton.length.toInt()}”x${_carton.width.toInt()}”x${_carton.height.toInt()}" ? "${_carton.length.toInt()}”x${_carton.width.toInt()}”x${_carton.height.toInt()}"
: null; : null;
@@ -178,15 +196,19 @@ class _CartonInfoState extends State<CartonInfo> {
final packageLengthBox = DisplayText( final packageLengthBox = DisplayText(
showLabelLink: true, showLabelLink: true,
subText: Text(numberFormatter.format(_packages.length)), subText: Text(numberFormatter.format(_packageCount)),
labelTextKey: "box.package", labelTextKey: "box.package",
onTapLabel: () { onTapLabel: () {
Navigator.push( Navigator.push(
context, context,
CupertinoPageRoute( CupertinoPageRoute(
builder: (context) => PackageDetailList( builder: (context) => PackageDetailList(
cartonNumber: _carton.cartonNumber ?? '', cartonNumber: _carton.cartonNumber ?? '',
packages: _packages))); packageCount: _packageCount,
senderID: _carton.senderID ?? "",
consingeeID: _carton.consigneeID ?? "",
fcsShipmentID: _carton.fcsShipmentID ?? "",
)));
}, },
); );
@@ -278,7 +300,8 @@ class _CartonInfoState extends State<CartonInfo> {
_cargoTypes.isNotEmpty _cargoTypes.isNotEmpty
? Padding( ? Padding(
padding: EdgeInsets.only(right: 0), padding: EdgeInsets.only(right: 0),
child: Text("${removeTrailingZeros(totalWeight)} lb", child: Text(
"${twoDecimalFormatted(double.tryParse(removeTrailingZeros(_totalWeight)) ?? 0)} lb",
textAlign: TextAlign.end, textAlign: TextAlign.end,
style: TextStyle(color: Colors.black54, fontSize: 15))) style: TextStyle(color: Colors.black54, fontSize: 15)))
: const SizedBox() : const SizedBox()
@@ -307,7 +330,7 @@ class _CartonInfoState extends State<CartonInfo> {
color: Colors.black, fontSize: 15), color: Colors.black, fontSize: 15),
), ),
Text( Text(
"${removeTrailingZeros(e.value.weight)} lb", "${twoDecimalFormatted(double.tryParse(removeTrailingZeros(e.value.weight)) ?? 0)} lb",
textAlign: TextAlign.end, textAlign: TextAlign.end,
style: TextStyle( style: TextStyle(
color: Colors.black, fontSize: 15)) color: Colors.black, fontSize: 15))
@@ -528,7 +551,7 @@ class _CartonInfoState extends State<CartonInfo> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Flexible(child: cartonSizeBox), Flexible(child: cartonSizeBox),
_packages.isEmpty _packageCount == 0
? const SizedBox() ? const SizedBox()
: Flexible(child: packageLengthBox), : Flexible(child: packageLengthBox),
], ],

View File

@@ -13,12 +13,10 @@ import '../../../domain/vo/local_step.dart';
import '../../../helpers/theme.dart'; import '../../../helpers/theme.dart';
import '../../domain/entities/cargo_type.dart'; import '../../domain/entities/cargo_type.dart';
import '../../domain/entities/carton.dart'; import '../../domain/entities/carton.dart';
import '../../domain/entities/package.dart';
import '../../domain/entities/user.dart'; import '../../domain/entities/user.dart';
import '../carton_size/model/carton_size_model.dart'; import '../carton_size/model/carton_size_model.dart';
import '../fcs_shipment/model/fcs_shipment_model.dart'; import '../fcs_shipment/model/fcs_shipment_model.dart';
import '../main/util.dart'; import '../main/util.dart';
import '../package/model/package_model.dart';
import '../widgets/local_text.dart'; import '../widgets/local_text.dart';
import '../widgets/progress.dart'; import '../widgets/progress.dart';
import '../widgets/step_widget.dart'; import '../widgets/step_widget.dart';
@@ -26,7 +24,7 @@ import 'cargo_widget.dart';
import 'carton_size_widget.dart'; import 'carton_size_widget.dart';
import 'carton_submit.dart'; import 'carton_submit.dart';
import 'model/package_selection_model.dart'; import 'model/package_selection_model.dart';
import 'packages_widget.dart'; import 'package_selection_widget.dart';
class CartonPackageEditor extends StatefulWidget { class CartonPackageEditor extends StatefulWidget {
final Carton carton; final Carton carton;
@@ -46,7 +44,7 @@ class _CartonPackageEditorState extends State<CartonPackageEditor> {
LocalStep(lable: 'Cargos', stepType: StepType.CARGOS), LocalStep(lable: 'Cargos', stepType: StepType.CARGOS),
LocalStep(lable: 'Submit', stepType: StepType.SUBMIT) LocalStep(lable: 'Submit', stepType: StepType.SUBMIT)
]; ];
List<Package> _packages = [];
List<CargoType> _cargoTypes = []; List<CargoType> _cargoTypes = [];
List<CargoType> _surchareItems = []; List<CargoType> _surchareItems = [];
@@ -86,8 +84,11 @@ class _CartonPackageEditorState extends State<CartonPackageEditor> {
_billToValue = widget.carton.billTo ?? billToSender; _billToValue = widget.carton.billTo ?? billToSender;
_selectedLastMile = widget.carton.lastMile ?? delivery_caton; _selectedLastMile = widget.carton.lastMile ?? delivery_caton;
_cargoTypes = widget.carton.cargoTypes;
_surchareItems = widget.carton.surchareItems; _cargoTypes =
widget.carton.cargoTypes.map((e) => e.cloneForCarton()).toList();
_surchareItems =
widget.carton.surchareItems.map((e) => e.cloneForSurchage()).toList();
// check carton size type // check carton size type
List<CartonSize> cartonSizes = context.read<CartonSizeModel>().cartonSizes; List<CartonSize> cartonSizes = context.read<CartonSizeModel>().cartonSizes;
@@ -120,11 +121,6 @@ class _CartonPackageEditorState extends State<CartonPackageEditor> {
.read<FcsShipmentModel>() .read<FcsShipmentModel>()
.getFcsShipment(widget.carton.fcsShipmentID ?? ""); .getFcsShipment(widget.carton.fcsShipmentID ?? "");
_shipment = s; _shipment = s;
_packages = await context
.read<PackageModel>()
.getPackagesByIds(widget.carton.packageIDs);
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
} }
@@ -233,19 +229,17 @@ class _CartonPackageEditorState extends State<CartonPackageEditor> {
)); ));
} else if (step.stepType == StepType.PACKAGES) { } else if (step.stepType == StepType.PACKAGES) {
return Expanded( return Expanded(
child: PackagesWidget( child: PackageSelectionWidget(
sender: _sender!, sender: _sender!,
consignee: _consignee!, consignee: _consignee!,
shipment: _shipment!, shipment: _shipment!,
onContinue: (packages) { onContinue: () {
setState(() { setState(() {
_packages = List.from(packages);
currentStep += 1; currentStep += 1;
}); });
}, },
onPrevious: (packages) { onPrevious: () {
setState(() { setState(() {
_packages = List.from(packages);
currentStep -= 1; currentStep -= 1;
}); });
}, },
@@ -288,7 +282,7 @@ class _CartonPackageEditorState extends State<CartonPackageEditor> {
height: _height, height: _height,
lastMile: _selectedLastMile, lastMile: _selectedLastMile,
shipment: _shipment!, shipment: _shipment!,
packages: _packages, // packages: _packages,
cargoTypes: _cargoTypes, cargoTypes: _cargoTypes,
surchareItems: _surchareItems, surchareItems: _surchareItems,
onCreate: () { onCreate: () {
@@ -340,7 +334,7 @@ class _CartonPackageEditorState extends State<CartonPackageEditor> {
length: length, length: length,
width: width, width: width,
height: height, height: height,
packages: _packages, // packages: _packages,
cargoTypes: _cargoTypes, cargoTypes: _cargoTypes,
surchareItems: _surchareItems); surchareItems: _surchareItems);

View File

@@ -12,7 +12,6 @@ import '../../../domain/entities/fcs_shipment.dart';
import '../../../domain/vo/local_step.dart'; import '../../../domain/vo/local_step.dart';
import '../../../helpers/theme.dart'; import '../../../helpers/theme.dart';
import '../../domain/entities/cargo_type.dart'; import '../../domain/entities/cargo_type.dart';
import '../../domain/entities/package.dart';
import '../../domain/entities/user.dart'; import '../../domain/entities/user.dart';
import '../main/util.dart'; import '../main/util.dart';
import '../widgets/local_text.dart'; import '../widgets/local_text.dart';
@@ -23,7 +22,7 @@ import 'carton_size_widget.dart';
import 'carton_submit.dart'; import 'carton_submit.dart';
import 'model/carton_model.dart'; import 'model/carton_model.dart';
import 'model/package_selection_model.dart'; import 'model/package_selection_model.dart';
import 'packages_widget.dart'; import 'package_selection_widget.dart';
class CartonPackageForm extends StatefulWidget { class CartonPackageForm extends StatefulWidget {
final User sender; final User sender;
@@ -48,7 +47,7 @@ class _CartonPackageFormState extends State<CartonPackageForm> {
LocalStep(lable: 'Cargos', stepType: StepType.CARGOS), LocalStep(lable: 'Cargos', stepType: StepType.CARGOS),
LocalStep(lable: 'Submit', stepType: StepType.SUBMIT) LocalStep(lable: 'Submit', stepType: StepType.SUBMIT)
]; ];
List<Package> _packages = [];
List<CargoType> _cargoTypes = []; List<CargoType> _cargoTypes = [];
List<CargoType> _surchareItems = []; List<CargoType> _surchareItems = [];
@@ -166,19 +165,17 @@ class _CartonPackageFormState extends State<CartonPackageForm> {
)); ));
} else if (step.stepType == StepType.PACKAGES) { } else if (step.stepType == StepType.PACKAGES) {
return Expanded( return Expanded(
child: PackagesWidget( child: PackageSelectionWidget(
sender: widget.sender, sender: widget.sender,
consignee: widget.consignee, consignee: widget.consignee,
shipment: _shipment!, shipment: _shipment!,
onContinue: (packages) { onContinue: () {
setState(() { setState(() {
_packages = List.from(packages);
currentStep += 1; currentStep += 1;
}); });
}, },
onPrevious: (packages) { onPrevious: () {
setState(() { setState(() {
_packages = List.from(packages);
currentStep -= 1; currentStep -= 1;
}); });
}, },
@@ -220,7 +217,6 @@ class _CartonPackageFormState extends State<CartonPackageForm> {
height: _height, height: _height,
lastMile: _selectedLastMile, lastMile: _selectedLastMile,
shipment: _shipment!, shipment: _shipment!,
packages: _packages,
cargoTypes: _cargoTypes, cargoTypes: _cargoTypes,
surchareItems: _surchareItems, surchareItems: _surchareItems,
onCreate: () { onCreate: () {
@@ -236,7 +232,6 @@ class _CartonPackageFormState extends State<CartonPackageForm> {
} }
} }
_create() async { _create() async {
setState(() { setState(() {
_isLoading = true; _isLoading = true;
@@ -272,7 +267,7 @@ class _CartonPackageFormState extends State<CartonPackageForm> {
length: length, length: length,
width: width, width: width,
height: height, height: height,
packages: _packages, // packages: _packages,
cargoTypes: _cargoTypes, cargoTypes: _cargoTypes,
surchareItems: _surchareItems); surchareItems: _surchareItems);
var c = await context.read<CartonModel>().createCarton(carton); var c = await context.read<CartonModel>().createCarton(carton);

View File

@@ -7,8 +7,6 @@ import '../../../domain/entities/cargo_type.dart';
import '../../../domain/entities/carton_size.dart'; import '../../../domain/entities/carton_size.dart';
import '../../../domain/entities/fcs_shipment.dart'; import '../../../domain/entities/fcs_shipment.dart';
import '../../../helpers/theme.dart'; import '../../../helpers/theme.dart';
import '../../domain/entities/package.dart';
import '../../domain/entities/user.dart'; import '../../domain/entities/user.dart';
import '../main/util.dart'; import '../main/util.dart';
import '../widgets/local_text.dart'; import '../widgets/local_text.dart';
@@ -24,7 +22,7 @@ class CartonSubmit extends StatelessWidget {
final User consingee; final User consingee;
final String billToValue; final String billToValue;
final FcsShipment shipment; final FcsShipment shipment;
final List<Package> packages; // final List<Package> packages;
final String cartonSizeType; final String cartonSizeType;
final CartonSize? standardSize; final CartonSize? standardSize;
final double length; final double length;
@@ -44,7 +42,7 @@ class CartonSubmit extends StatelessWidget {
this.onCreate, this.onCreate,
this.onPrevious, this.onPrevious,
required this.shipment, required this.shipment,
this.packages = const [], // this.packages = const [],
this.standardSize, this.standardSize,
required this.cartonSizeType, required this.cartonSizeType,
required this.lastMile, required this.lastMile,
@@ -196,42 +194,42 @@ class CartonSubmit extends StatelessWidget {
), ),
); );
final packagesBox = Padding( // final packagesBox = Padding(
padding: const EdgeInsets.only(top: 10), // padding: const EdgeInsets.only(top: 10),
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
Padding( // Padding(
padding: const EdgeInsets.only(left: 5, bottom: 5), // padding: const EdgeInsets.only(left: 5, bottom: 5),
child: LocalText(context, 'box.package.count', // child: LocalText(context, 'box.package.count',
translationVariables: [packages.length.toString()], // translationVariables: [packages.length.toString()],
color: primaryColor, // color: primaryColor,
fontSize: 16, // fontSize: 16,
fontWeight: FontWeight.normal), // fontWeight: FontWeight.normal),
), // ),
Container( // Container(
decoration: BoxDecoration( // decoration: BoxDecoration(
border: Border.all(color: primaryColor), // border: Border.all(color: primaryColor),
borderRadius: BorderRadius.circular(5), // borderRadius: BorderRadius.circular(5),
), // ),
child: Padding( // child: Padding(
padding: const EdgeInsets.all(8.0), // padding: const EdgeInsets.all(8.0),
child: Wrap( // child: Wrap(
spacing: 15, // spacing: 15,
children: packages.map((e) { // children: packages.map((e) {
return SizedBox( // return SizedBox(
width: MediaQuery.of(context).size.width / 2.5, // width: MediaQuery.of(context).size.width / 2.5,
child: Padding( // child: Padding(
padding: const EdgeInsets.symmetric(vertical: 3), // padding: const EdgeInsets.symmetric(vertical: 3),
child: Text( // child: Text(
e.trackingID ?? "", // e.trackingID ?? "",
style: TextStyle(color: Colors.black, fontSize: 15), // style: TextStyle(color: Colors.black, fontSize: 15),
), // ),
), // ),
); // );
}).toList()), // }).toList()),
), // ),
), // ),
]), // ]),
); // );
final cargosBox = Padding( final cargosBox = Padding(
padding: const EdgeInsets.only(top: 10), padding: const EdgeInsets.only(top: 10),
@@ -245,7 +243,8 @@ class CartonSubmit extends StatelessWidget {
color: primaryColor, color: primaryColor,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.normal), fontWeight: FontWeight.normal),
Text("${removeTrailingZeros(totalWeight)} lb", Text(
"${twoDecimalFormatted(double.tryParse(removeTrailingZeros(totalWeight)) ?? 0)} lb",
style: TextStyle(color: Colors.black, fontSize: 15)) style: TextStyle(color: Colors.black, fontSize: 15))
], ],
), ),
@@ -275,7 +274,8 @@ class CartonSubmit extends StatelessWidget {
style: TextStyle( style: TextStyle(
color: Colors.black, fontSize: 15), color: Colors.black, fontSize: 15),
), ),
Text("${removeTrailingZeros(e.weight)} lb", Text(
"${twoDecimalFormatted(double.tryParse(removeTrailingZeros(e.weight)) ?? 0)} lb",
style: TextStyle( style: TextStyle(
color: Colors.black, fontSize: 15)) color: Colors.black, fontSize: 15))
], ],
@@ -448,11 +448,11 @@ class CartonSubmit extends StatelessWidget {
const SizedBox(height: 10), const SizedBox(height: 10),
shipmentBox, shipmentBox,
const SizedBox(height: 10), const SizedBox(height: 10),
packages.isNotEmpty ? packagesBox : const SizedBox(), // packages.isNotEmpty ? packagesBox : const SizedBox(),
// const SizedBox(height: 10),
cargoTypes.isNotEmpty ? cargosBox : const SizedBox(),
const SizedBox(height: 10), const SizedBox(height: 10),
cargosBox, surchareItems.isNotEmpty ? surChargeItemsBox : const SizedBox(),
const SizedBox(height: 10),
surChargeItemsBox,
const SizedBox(height: 20), const SizedBox(height: 20),
], ],
), ),

View File

@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_vector_icons/flutter_vector_icons.dart'; import 'package:flutter_vector_icons/flutter_vector_icons.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../main/util.dart';
import '../widgets/local_text.dart'; import '../widgets/local_text.dart';
import 'carton_info.dart'; import 'carton_info.dart';
@@ -99,7 +100,7 @@ class MixCartonDetailList extends StatelessWidget {
), ),
), ),
Text( Text(
"${carton.cartonWeight.toStringAsFixed(2)} lb", "${twoDecimalFormatted(carton.cartonWeight)} lb",
style: TextStyle(fontSize: 14.0, color: Colors.grey), style: TextStyle(fontSize: 14.0, color: Colors.grey),
) )
], ],

View File

@@ -5,59 +5,68 @@ import 'package:logging/logging.dart';
import '../../../constants.dart'; import '../../../constants.dart';
import '../../../domain/entities/package.dart'; import '../../../domain/entities/package.dart';
import '../../../pagination/paginator_listener.dart';
import '../../main/model/base_model.dart'; import '../../main/model/base_model.dart';
class PackageSelectionModel extends BaseModel { class PackageSelectionModel extends BaseModel {
final log = Logger("PackageSelectionModel"); final log = Logger("PackageSelectionModel");
String query = ""; PaginatorListener<Package>? getPackages;
List<Package> packages = [];
List<Package> selectedPackages = [];
List<Package> packages = [];
bool isLoading = false; bool isLoading = false;
DocumentSnapshot? _lastDocument; DocumentSnapshot? _lastDocument;
bool ended = false; bool ended = false;
Timer? t; @override
search(String term, logout() async {
{bool imm = false,
required String shipmentId,
required String senderId,
required String consigneeId}) async {
query = term;
packages.clear(); packages.clear();
_lastDocument = null; getPackages?.close();
ended = false; }
t?.cancel();
t = Timer(Duration(milliseconds: imm ? 0 : 800), () async { loadPackages(
await refresh( {required String senderId,
term: term, required String consigneeId,
shipmentId: shipmentId, required String shipmentId}) {
consigneeId: consigneeId, if (user == null) return;
senderId: senderId);
}); String path = "/$packages_collection";
Query col = FirebaseFirestore.instance
.collection(path)
.where("sender_id", isEqualTo: senderId)
.where("user_id", isEqualTo: consigneeId)
.where("fcs_shipment_id", isEqualTo: shipmentId);
Query pageQuery = FirebaseFirestore.instance
.collection(path)
.where("sender_id", isEqualTo: senderId)
.where("user_id", isEqualTo: consigneeId)
.where("fcs_shipment_id", isEqualTo: shipmentId);
pageQuery = pageQuery.orderBy("created_date", descending: true);
getPackages?.close();
getPackages = PaginatorListener<Package>(
col, pageQuery, (data, id) => Package.fromMap(data, id),
rowPerLoad: 30);
} }
Future<void> refresh( Future<void> refresh(
{required String shipmentId, {required String shipmentId,
required String senderId, required String senderId,
required String consigneeId, required String consigneeId}) async {
String term = ""}) async {
packages.clear(); packages.clear();
_lastDocument = null; _lastDocument = null;
ended = false; ended = false;
await loadMoreData( await loadMoreData(
shipmentId: shipmentId, shipmentId: shipmentId,
senderId: senderId, senderId: senderId,
consigneeId: consigneeId, consigneeId: consigneeId,
term: term); );
notifyListeners(); notifyListeners();
} }
Future<void> loadMoreData( Future<void> loadMoreData(
{required String shipmentId, {required String shipmentId,
required String senderId, required String senderId,
required String consigneeId, required String consigneeId}) async {
String term = ""}) async {
int rowPerPage = 20; int rowPerPage = 20;
try { try {
@@ -69,12 +78,9 @@ class PackageSelectionModel extends BaseModel {
whereIn: [package_processed_status, package_packed_status]) whereIn: [package_processed_status, package_packed_status])
.where("sender_id", isEqualTo: senderId) .where("sender_id", isEqualTo: senderId)
.where("user_id", isEqualTo: consigneeId) .where("user_id", isEqualTo: consigneeId)
.where("fcs_shipment_id", isEqualTo: shipmentId)
.where("is_deleted", isEqualTo: false); .where("is_deleted", isEqualTo: false);
if (term != "") {
query = query.where("tracking_id", isEqualTo: term);
}
query = query.orderBy("created_date", descending: true); query = query.orderBy("created_date", descending: true);
if (_lastDocument != null) { if (_lastDocument != null) {
@@ -92,10 +98,6 @@ class PackageSelectionModel extends BaseModel {
return p; return p;
}).toList(); }).toList();
for (var p in list) {
selectedPackages.contains(p) ? p.isChecked = true : p.isChecked = false;
}
packages.addAll(list); packages.addAll(list);
if (list.length < rowPerPage) ended = true; if (list.length < rowPerPage) ended = true;
notifyListeners(); notifyListeners();
@@ -106,22 +108,8 @@ class PackageSelectionModel extends BaseModel {
} }
} }
selectPackage(Package a) {
if (a.isChecked) {
selectedPackages.add(a);
} else {
selectedPackages.remove(a);
}
}
addSelectedPackage(List<Package> list) {
selectedPackages = list;
}
clearSelection() { clearSelection() {
selectedPackages.clear();
packages.clear(); packages.clear();
query = "";
} }
Future<List<Package>> getPackagesBySenderAndConsigneeId( Future<List<Package>> getPackagesBySenderAndConsigneeId(
@@ -174,4 +162,21 @@ class PackageSelectionModel extends BaseModel {
} }
return list; return list;
} }
Future<int?> getPackageCount(
{required String senderId,
required String consigneeId,
required String shipmentId}) async {
String path = "/$packages_collection";
AggregateQuerySnapshot query = await FirebaseFirestore.instance
.collection(path)
.where("sender_id", isEqualTo: senderId)
.where("user_id", isEqualTo: consigneeId)
.where("fcs_shipment_id", isEqualTo: shipmentId)
.count()
.get();
return query.count;
}
} }

View File

@@ -1,24 +1,54 @@
import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/entities/package.dart';
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/carton/model/package_selection_model.dart';
import 'package:fcs/pages/package/package_info.dart'; import 'package:fcs/pages/package/package_info.dart';
import 'package:fcs/pages/widgets/local_app_bar.dart'; import 'package:fcs/pages/widgets/local_app_bar.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_vector_icons/flutter_vector_icons.dart'; import 'package:flutter_vector_icons/flutter_vector_icons.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../../pagination/paginator_listview.dart';
import '../widgets/local_text.dart'; import '../widgets/local_text.dart';
class PackageDetailList extends StatelessWidget { class PackageDetailList extends StatefulWidget {
final String cartonNumber; final String cartonNumber;
final List<Package> packages; final int packageCount;
PackageDetailList( final String senderID;
{super.key, required this.cartonNumber, required this.packages}); final String consingeeID;
final String fcsShipmentID;
const PackageDetailList(
{super.key,
required this.cartonNumber,
required this.packageCount,
required this.senderID,
required this.consingeeID,
required this.fcsShipmentID});
@override
State<PackageDetailList> createState() => _PackageDetailListState();
}
class _PackageDetailListState extends State<PackageDetailList> {
final NumberFormat numberFormatter = NumberFormat("#,###"); final NumberFormat numberFormatter = NumberFormat("#,###");
@override
void initState() {
_init();
super.initState();
}
_init() {
context.read<PackageSelectionModel>().loadPackages(
senderId: widget.senderID,
consigneeId: widget.consingeeID,
shipmentId: widget.fcsShipmentID);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var packageModel = context.watch<PackageSelectionModel>();
return Scaffold( return Scaffold(
appBar: LocalAppBar( appBar: LocalAppBar(
backgroundColor: Colors.white, backgroundColor: Colors.white,
@@ -31,81 +61,81 @@ class PackageDetailList extends StatelessWidget {
"box.package.count", "box.package.count",
fontSize: 20, fontSize: 20,
color: primaryColor, color: primaryColor,
translationVariables: [numberFormatter.format(packages.length)], translationVariables: [
numberFormatter.format(widget.packageCount)
],
), ),
Text(cartonNumber, Text(widget.cartonNumber,
style: TextStyle(fontSize: 15, color: Colors.black)) style: TextStyle(fontSize: 15, color: Colors.black))
], ],
), ),
), ),
body: ListView.separated( body: PaginatorListView<Package>(
separatorBuilder: (context, index) => paginatorListener: packageModel.getPackages!,
Divider(height: 1, color: dividerColor), rowBuilder: (p) {
itemCount: packages.length, Package package = p;
itemBuilder: (BuildContext context, int index) { return InkWell(
var package = packages[index]; onTap: () {
return InkWell( Navigator.push(
onTap: () { context,
Navigator.push( CupertinoPageRoute(
context, builder: (context) => PackageInfo(package: package)),
CupertinoPageRoute( );
builder: (context) => PackageInfo(package: package)), },
); child: Container(
}, padding: EdgeInsets.only(left: 15, right: 15),
child: Container( child: Column(
padding: EdgeInsets.only(left: 15, right: 15), children: [
child: Column( Row(
children: [ children: <Widget>[
Row( Expanded(
children: <Widget>[ child: Padding(
Expanded( padding: const EdgeInsets.symmetric(vertical: 10),
child: Padding( child: Row(
padding: const EdgeInsets.symmetric(vertical: 10), children: <Widget>[
child: Row( Icon(Octicons.package, color: primaryColor),
children: <Widget>[ Expanded(
Icon(Octicons.package, color: primaryColor), child: Padding(
Expanded( padding: const EdgeInsets.only(left: 15),
child: Padding( child: Column(
padding: const EdgeInsets.only(left: 15), crossAxisAlignment:
child: Column( CrossAxisAlignment.start,
crossAxisAlignment: children: <Widget>[
CrossAxisAlignment.start, Text(
children: <Widget>[ package.id == null
Text(
package.id == null
? ''
: package.trackingID!,
style: TextStyle(
fontSize: 15.0,
color: Colors.black),
),
Padding(
padding:
const EdgeInsets.only(top: 5),
child: Text(
package.market == null
? '' ? ''
: package.market!, : package.trackingID!,
style: TextStyle( style: TextStyle(
fontSize: 15.0, fontSize: 15.0,
color: Colors.grey), color: Colors.black),
), ),
), Padding(
], padding:
const EdgeInsets.only(top: 5),
child: Text(
package.market == null
? ''
: package.market!,
style: TextStyle(
fontSize: 15.0,
color: Colors.grey),
),
),
],
),
), ),
), ),
), ],
], ),
), ),
), ),
), ],
], ),
), ],
], ),
), ),
), );
); },
}, color: primaryColor));
));
} }
} }

View File

@@ -74,32 +74,38 @@ class PackageSelectionResult extends StatelessWidget {
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: new Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 8.0), vertical: 8.0),
child: new Column( child: Column(
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.start, CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
new Text(package.trackingID ?? "", Text(package.trackingID ?? "",
style: new TextStyle( style: TextStyle(
fontSize: 15.0, fontSize: 15.0,
color: Colors.black)), color: Colors.black)),
new Text( Text(
package.cartonIds.isEmpty package.market != null &&
? "-" package.market != ""
: "${numberFormatter.format(package.cartonIds.length)} Boxes", ? "${package.market}"
style: new TextStyle( : "-",
style: TextStyle(
fontSize: 15.0, fontSize: 15.0,
color: Colors.grey), color: Colors.grey),
), ),
// Text(
// package.cartonIds.isEmpty
// ? "-"
// : "${numberFormatter.format(package.cartonIds.length)} Boxes",
// style: TextStyle(
// fontSize: 15.0,
// color: Colors.grey),
// ),
], ],
), ),
), ),
), ),
package.isChecked
? Icon(Icons.check, color: primaryColor)
: const SizedBox()
], ],
), ),
), ),

View File

@@ -1,5 +1,5 @@
import 'package:fcs/domain/entities/fcs_shipment.dart'; import 'package:fcs/domain/entities/fcs_shipment.dart';
import 'package:fcs/helpers/theme.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_icons_null_safety/flutter_icons_null_safety.dart'; import 'package:flutter_icons_null_safety/flutter_icons_null_safety.dart';
@@ -8,27 +8,25 @@ import 'package:provider/provider.dart';
import '../../domain/entities/package.dart'; import '../../domain/entities/package.dart';
import '../../domain/entities/user.dart'; import '../../domain/entities/user.dart';
import '../main/util.dart'; import '../main/util.dart';
import '../widgets/barcode_scanner.dart'; import '../package/package_info.dart';
import '../widgets/continue_button.dart'; import '../widgets/continue_button.dart';
import '../widgets/local_title.dart'; import '../widgets/local_title.dart';
import '../widgets/previous_button.dart'; import '../widgets/previous_button.dart';
import 'model/package_selection_model.dart'; import 'model/package_selection_model.dart';
import 'package_selection_result.dart'; import 'package_selection_result.dart';
typedef OnPrevious = Function(List<Package> packages); typedef OnPrevious = Function();
typedef OnContinue = Function(List<Package> packages); typedef OnContinue = Function();
class PackageSelectionWidget extends StatefulWidget { class PackageSelectionWidget extends StatefulWidget {
final User sender; final User sender;
final User consignee; final User consignee;
final FcsShipment shipment; final FcsShipment shipment;
final List<Package> packages;
final OnPrevious? onPrevious; final OnPrevious? onPrevious;
final OnContinue? onContinue; final OnContinue? onContinue;
const PackageSelectionWidget({ const PackageSelectionWidget({
super.key, super.key,
required this.packages,
this.onPrevious, this.onPrevious,
this.onContinue, this.onContinue,
required this.shipment, required this.shipment,
@@ -41,8 +39,6 @@ class PackageSelectionWidget extends StatefulWidget {
} }
class _PackageSelectionWidgetState extends State<PackageSelectionWidget> { class _PackageSelectionWidgetState extends State<PackageSelectionWidget> {
final TextEditingController _controller = TextEditingController();
String _query = "";
bool _isLoadMore = false; bool _isLoadMore = false;
final _scrollController = ScrollController(); final _scrollController = ScrollController();
bool _down = true; bool _down = true;
@@ -60,29 +56,18 @@ class _PackageSelectionWidgetState extends State<PackageSelectionWidget> {
} }
_init() { _init() {
var searchModel = context.read<PackageSelectionModel>(); var packageModel = context.read<PackageSelectionModel>();
_controller.text = searchModel.query; packageModel.refresh(
_query = searchModel.query; shipmentId: widget.shipment.id!,
consigneeId: widget.consignee.id!,
searchModel.refresh( senderId: widget.sender.id!,
shipmentId: widget.shipment.id!, );
consigneeId: widget.consignee.id!,
senderId: widget.sender.id!,
term: _query);
searchModel.addSelectedPackage(widget.packages);
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
} }
} }
// @override
// void didUpdateWidget(covariant PackageSelectionWidget oldWidget) {
// _init();
// super.didUpdateWidget(oldWidget);
// }
Future<void> _loadMoreData() async { Future<void> _loadMoreData() async {
if (_isLoadMore) return; if (_isLoadMore) return;
var model = context.read<PackageSelectionModel>(); var model = context.read<PackageSelectionModel>();
@@ -92,10 +77,10 @@ class _PackageSelectionWidgetState extends State<PackageSelectionWidget> {
}); });
await model.loadMoreData( await model.loadMoreData(
shipmentId: widget.shipment.id!, shipmentId: widget.shipment.id!,
consigneeId: widget.consignee.id!, consigneeId: widget.consignee.id!,
senderId: widget.sender.id!, senderId: widget.sender.id!,
term: _query); );
setState(() { setState(() {
_isLoadMore = false; _isLoadMore = false;
@@ -105,8 +90,7 @@ class _PackageSelectionWidgetState extends State<PackageSelectionWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var model = context.watch<PackageSelectionModel>(); var model = context.watch<PackageSelectionModel>();
List<Package> searchResults = model.packages; List<Package> packages = model.packages;
List<Package> selectedPackageList = model.selectedPackages;
final senderBox = userDisplayBox(context, final senderBox = userDisplayBox(context,
lableKey: "box.sender.title", lableKey: "box.sender.title",
@@ -132,103 +116,23 @@ class _PackageSelectionWidgetState extends State<PackageSelectionWidget> {
final continueBtn = ContinueButton( final continueBtn = ContinueButton(
onTap: () { onTap: () {
if (selectedPackageList.isEmpty || searchResults.isEmpty) { if (packages.isEmpty) {
showMsgDialog(context, 'Error', "Please select the packages"); showMsgDialog(context, 'Error', "Please add the packages");
return false; return false;
} }
if (widget.onContinue != null) { if (widget.onContinue != null) {
widget.onContinue!(selectedPackageList); widget.onContinue!();
} }
}, },
); );
final previousBtn = PreviousButton(onTap: () { final previousBtn = PreviousButton(onTap: () {
if (widget.onPrevious != null) { if (widget.onPrevious != null) {
widget.onPrevious!(selectedPackageList); widget.onPrevious!();
} }
}); });
final searchBox = SizedBox(
height: 40,
child: TextField(
controller: _controller,
cursorColor: primaryColor,
onSubmitted: (value) {
setState(() {
_query = value;
});
_search(imm: true);
},
onChanged: (v) {
setState(() {
_query = v;
});
_search();
},
decoration: InputDecoration(
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: labelColor),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
borderSide:
BorderSide(color: labelColor.withOpacity(0.3), width: 0)),
focusedBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
borderSide: BorderSide(color: labelColor),
),
hintText: "Search by tracking number",
hintStyle: const TextStyle(
color: Colors.grey,
fontSize: 16,
fontWeight: FontWeight.normal),
suffixIcon: Row(
mainAxisSize: MainAxisSize.min,
children: [
InkResponse(
radius: 20,
onTap: () {
_scan(context);
},
child: const Icon(Icons.qr_code_scanner,
color: Colors.black87)),
IconButton(
splashRadius: 20,
onPressed: () {
setState(() {
_controller.clear();
_query = "";
});
_search();
},
icon: const Icon(Icons.close, color: Colors.black87)),
],
),
contentPadding: const EdgeInsets.all(10),
filled: true),
),
);
final selectedPackageBox = Align(
alignment: Alignment.topLeft,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.only(top: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: selectedPackageList
.map((e) => Padding(
padding: const EdgeInsets.only(right: 20),
child: Text(e.trackingID ?? "",
style:
TextStyle(color: Colors.black, fontSize: 15)),
))
.toList())),
);
return Column( return Column(
children: [ children: [
Expanded( Expanded(
@@ -255,8 +159,6 @@ class _PackageSelectionWidgetState extends State<PackageSelectionWidget> {
LocalTitle( LocalTitle(
textKey: "box.select.package", topPadding: 5), textKey: "box.select.package", topPadding: 5),
const SizedBox(height: 10), const SizedBox(height: 10),
searchBox,
selectedPackageBox,
]) ])
: const SizedBox(), : const SizedBox(),
), ),
@@ -273,11 +175,12 @@ class _PackageSelectionWidgetState extends State<PackageSelectionWidget> {
_down = true; _down = true;
}); });
}, },
onTap: (a) async { onTap: (a) {
setState(() { Navigator.push(
a.isChecked = !a.isChecked; context,
}); CupertinoPageRoute(
context.read<PackageSelectionModel>().selectPackage(a); builder: (context) => PackageInfo(package: a)),
);
}, },
), ),
), ),
@@ -302,31 +205,4 @@ class _PackageSelectionWidgetState extends State<PackageSelectionWidget> {
], ],
); );
} }
_scan(BuildContext context) async {
try {
String? barcode = await scanBarcode();
if (barcode != null) {
setState(() {
_controller.text = barcode;
_query = barcode;
});
await _search();
}
} catch (e) {
showMsgDialog(context, 'Error', e.toString());
}
}
_search({bool imm = false}) async {
try {
await context.read<PackageSelectionModel>().search(_query,
imm: imm,
shipmentId: widget.shipment.id!,
senderId: widget.sender.id!,
consigneeId: widget.consignee.id!);
} catch (e) {
showMsgDialog(context, 'Error', e.toString());
}
}
} }

View File

@@ -1,223 +0,0 @@
import 'package:fcs/domain/entities/fcs_shipment.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_icons_null_safety/flutter_icons_null_safety.dart';
import 'package:provider/provider.dart';
import '../../domain/entities/package.dart';
import '../../domain/entities/user.dart';
import '../main/util.dart';
import '../widgets/continue_button.dart';
import '../widgets/local_text.dart';
import '../widgets/local_title.dart';
import '../widgets/previous_button.dart';
import 'model/package_selection_model.dart';
import 'package_selection_result.dart';
typedef OnPrevious = Function(List<Package> packages);
typedef OnContinue = Function(List<Package> packages);
class PackagesWidget extends StatefulWidget {
final User sender;
final User consignee;
final FcsShipment shipment;
final OnPrevious? onPrevious;
final OnContinue? onContinue;
const PackagesWidget({
super.key,
this.onPrevious,
this.onContinue,
required this.shipment,
required this.sender,
required this.consignee,
});
@override
State<PackagesWidget> createState() => _PackagesWidgetState();
}
class _PackagesWidgetState extends State<PackagesWidget> {
final _scrollController = ScrollController();
bool _down = true;
List<Package> _packages = [];
bool _isLoading = false;
@override
void initState() {
_init();
super.initState();
_scrollController.addListener(() {
setState(() {
_down = _scrollController.position.userScrollDirection ==
ScrollDirection.forward;
});
});
}
_init() async {
_packages.clear();
_isLoading = true;
var packageModel = context.read<PackageSelectionModel>();
var list = await packageModel.getActivePackages(
shipmentId: widget.shipment.id!,
senderId: widget.sender.id!,
consigneeId: widget.consignee.id!);
_packages = List.from(list);
_isLoading = false;
if (mounted) {
setState(() {});
}
}
@override
Widget build(BuildContext context) {
final senderBox = userDisplayBox(context,
lableKey: "box.sender.title",
icon: MaterialCommunityIcons.account_arrow_right,
showLink: false,
name: widget.sender.name ?? "",
fcsID: widget.sender.fcsID ?? "");
final consigneeBox = userDisplayBox(context,
showLink: false,
lableKey: "box.consignee.title",
icon: MaterialCommunityIcons.account_arrow_left,
name: widget.consignee.name,
fcsID: widget.consignee.fcsID);
final userRow = Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(flex: 2, child: consigneeBox),
Flexible(child: senderBox)
],
);
final continueBtn = ContinueButton(
onTap: () {
if (_packages.isEmpty) {
showMsgDialog(context, 'Error', "Please add the packages");
return false;
}
if (widget.onContinue != null) {
widget.onContinue!(_packages);
}
},
);
final previousBtn = PreviousButton(onTap: () {
if (widget.onPrevious != null) {
widget.onPrevious!(_packages);
}
});
return Column(
children: [
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 10, right: 10),
child: Column(
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder:
(Widget child, Animation<double> animation) =>
FadeTransition(
opacity: animation,
child: SizeTransition(
sizeFactor: animation,
axis: Axis.vertical,
child: child,
),
),
child: _down
? Column(children: [
const SizedBox(height: 8),
userRow,
LocalTitle(
textKey: "box.select.package", topPadding: 5),
const SizedBox(height: 10),
])
: const SizedBox(),
),
Expanded(
child: _packages.isEmpty && !_isLoading
? Center(
child: LocalText(context, 'box.no_package',
color: Colors.black, fontSize: 15))
: RefreshIndicator(
color: primaryColor,
onRefresh: () async {
_init();
},
child: ListView.builder(
padding: const EdgeInsets.only(top: 10),
controller: _scrollController,
shrinkWrap: true,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
Package package = _packages[index];
return packageRow(context, package);
},
itemCount: _packages.length)),
),
],
),
),
),
widget.onContinue != null
? Padding(
padding: const EdgeInsets.only(left: 15, right: 15, top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
previousBtn,
continueBtn,
],
),
)
: const SizedBox(),
const SizedBox(height: 20)
],
);
}
Widget packageRow(BuildContext context, Package package) {
return Padding(
padding: const EdgeInsets.only(top: 5, bottom: 5),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5)),
border: Border.all(color: Colors.grey.shade300)),
padding: EdgeInsets.only(left: 10, right: 10),
child: Row(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(package.trackingID ?? "",
style: TextStyle(fontSize: 15.0, color: Colors.black)),
Text(
package.cartonIds.isEmpty
? "-"
: "${numberFormatter.format(package.cartonIds.length)} Boxes",
style: TextStyle(fontSize: 15.0, color: Colors.grey),
),
],
),
),
),
package.isChecked
? Icon(Icons.check, color: primaryColor)
: const SizedBox()
],
),
),
);
}
}

View File

@@ -4,15 +4,16 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_vector_icons/flutter_vector_icons.dart'; import 'package:flutter_vector_icons/flutter_vector_icons.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../../main/util.dart';
import '../carton_info.dart'; import '../carton_info.dart';
import '../print_qr_code_page.dart'; import '../print_qr_code_page.dart';
class CartonListRow extends StatelessWidget { class CartonListRow extends StatelessWidget {
final Carton box; final Carton box;
CartonListRow({Key? key, required this.box}) : super(key: key); CartonListRow({super.key, required this.box});
final double dotSize = 15.0; final double dotSize = 15.0;
final DateFormat dateFormat = new DateFormat("dd MMM yyyy"); final DateFormat dateFormat = DateFormat("dd MMM yyyy");
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -28,9 +29,9 @@ class CartonListRow extends StatelessWidget {
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: new Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0), padding: const EdgeInsets.symmetric(vertical: 10.0),
child: new Row( child: Row(
children: <Widget>[ children: <Widget>[
Container( Container(
padding: EdgeInsets.only(left: 0), padding: EdgeInsets.only(left: 0),
@@ -40,24 +41,24 @@ class CartonListRow extends StatelessWidget {
size: 30, size: 30,
), ),
), ),
new Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 15.0), padding: const EdgeInsets.only(left: 15.0),
child: Row( child: Row(
children: [ children: [
new Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
new Text( Text(
box.cartonNumber ?? "", box.cartonNumber ?? "",
style: new TextStyle( style: TextStyle(
fontSize: 15.0, color: Colors.black), fontSize: 15.0, color: Colors.black),
), ),
Padding( Padding(
padding: const EdgeInsets.only(top: 5), padding: const EdgeInsets.only(top: 5),
child: new Text( child: Text(
box.consigneeName ?? "", box.consigneeName ?? "",
style: new TextStyle( style: TextStyle(
fontSize: 15.0, color: Colors.grey), fontSize: 15.0, color: Colors.grey),
), ),
), ),
@@ -95,10 +96,9 @@ class CartonListRow extends StatelessWidget {
padding: const EdgeInsets.only(top: 5), padding: const EdgeInsets.only(top: 5),
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
new Text( Text(
"${box.cartonWeight.toStringAsFixed(2)} lb", "${twoDecimalFormatted(box.cartonWeight)} lb",
style: style: TextStyle(fontSize: 14.0, color: Colors.grey),
new TextStyle(fontSize: 14.0, color: Colors.grey),
), ),
], ],
), ),

View File

@@ -410,6 +410,11 @@ String removeTrailingZeros(double number) {
return result; return result;
} }
String twoDecimalFormatted(double number) {
String formattedString = number.toStringAsFixed(2);
return formattedString;
}
bool isValidEmail(String email) { bool isValidEmail(String email) {
// Define a regular expression for validating an email // Define a regular expression for validating an email
final emailRegex = RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$'); final emailRegex = RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$');
@@ -561,4 +566,3 @@ Widget settingRow(BuildContext context,
), ),
); );
} }