diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index 4b07884..d21ef28 100644 --- a/assets/local/localization_en.json +++ b/assets/local/localization_en.json @@ -266,7 +266,7 @@ "box.delivery_type":"Delivery type", "box.fcs_shipment_num":"Shipment", "box.fcs.id":"FCS ID", - "box.name":"Customer name", + "box.name":"Sender", "box.phone":"Phone number", "box.actual_weight":"Actual weight", "box.add_cargo":"Add cargo", @@ -326,6 +326,14 @@ "box.package_size":"Package", "box.select.cartion":"Select cartons", "box.no_carton":"There is no cartons in this shipment.", + "box.crete.carton":"Create carton", + "box.carton.type":"Carton Type", + "box.select.delivery":"Select delivery type", + "box.select.package":"Select packages", + "box.no_package":"There is no packages.", + "box.input_cargo_weight":"Input cargo weight (lb)", + "box.input_surcharge_item":"Input surcharge items", + "box.select.cargo_type":"Select surcharge item", "Boxes End ================================================================":"", "Delivery Start ================================================================":"", @@ -592,7 +600,7 @@ "processing.new":"New Processing", "processing.create":"New Processing", "processing.update":"Update Processing", - "processing.consignee.name":"Consignee name", + "processing.consignee.name":"Consignee", "processing.shipper.name":"Sender name", "processing.package.select.btn":"Select", "processing.package.create":"New Package", diff --git a/assets/local/localization_mu.json b/assets/local/localization_mu.json index e623fa8..44c0a1b 100644 --- a/assets/local/localization_mu.json +++ b/assets/local/localization_mu.json @@ -266,7 +266,7 @@ "box.delivery_type":"ပို့ဆောင်ရမည့်အမျိုးအစား", "box.fcs_shipment_num":"တင်ပို့နံပါတ်", "box.fcs.id":"FCS ID", - "box.name":"နာမည်", + "box.name":"ပေးပို့သူ", "box.package":"အထုပ်များ", "box.phone":"ဖုန်းနံပါတ်", "box.actual_weight":"အမှန်အလေးချိန်", @@ -325,6 +325,14 @@ "box.package_size":"Package", "box.select.cartion":"Select cartons", "box.no_carton":"There is no cartons in this shipment.", + "box.crete.carton":"Create carton", + "box.carton.type":"Carton Type", + "box.select.delivery":"Select delivery type", + "box.select.package":"Select packages", + "box.no_package":"There is no packages.", + "box.input_cargo_weight":"Input cargo weight (lb)", + "box.input_surcharge_item":"Input surcharge items", + "box.select.cargo_type":"Select surcharge item", "Boxes End ================================================================":"", "Delivery Start ================================================================":"", @@ -595,7 +603,7 @@ "processing.new":"လုပ်ဆောင်ခြင်း အသစ်", "processing.create":"လုပ်ဆောင်ခြင်း အသစ်", "processing.update":"လုပ်ဆောင်ခြင်း ပြင်ဆင်ခြင်း", - "processing.consignee.name":"လက်ခံသူ အမည်", + "processing.consignee.name":"လက်ခံသူ", "processing.shipper.name":"တင်ပို့သူ အမည်", "processing.package.select.btn":"ရွေးချယ်မည်", "processing.package.create":"အထုပ် အသစ်", diff --git a/lib/app.dart b/lib/app.dart index 1ca72ad..ce6c735 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -30,6 +30,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; import 'pages/carton/model/carton_selection_model.dart'; +import 'pages/carton/model/package_selection_model.dart'; import 'pages/delivery/model/delivery_model.dart'; class App extends StatefulWidget { @@ -64,6 +65,7 @@ class _AppState extends State { final ProcessingModel processingModel = new ProcessingModel(); final PickupModel pickupModel = new PickupModel(); final CartonSelectionModel cartonSelectionModel = new CartonSelectionModel(); + final PackageSelectionModel packageSelectionModel = new PackageSelectionModel(); late AppTranslationsDelegate _newLocaleDelegate; @@ -87,7 +89,8 @@ class _AppState extends State { ..addModel(cartonSizeModel) ..addModel(processingModel) ..addModel(pickupModel) - ..addModel(cartonSelectionModel); + ..addModel(cartonSelectionModel) + ..addModel(packageSelectionModel); _newLocaleDelegate = AppTranslationsDelegate( newLocale: Translation().supportedLocales().first); @@ -137,6 +140,7 @@ class _AppState extends State { ChangeNotifierProvider.value(value: processingModel), ChangeNotifierProvider.value(value: pickupModel), ChangeNotifierProvider.value(value: cartonSelectionModel), + ChangeNotifierProvider.value(value: packageSelectionModel), ], child: Consumer( builder: (context, value, child) { diff --git a/lib/domain/constants.dart b/lib/domain/constants.dart index ee355de..f2081a4 100644 --- a/lib/domain/constants.dart +++ b/lib/domain/constants.dart @@ -133,3 +133,7 @@ const invoice_paid_status = "paid"; const payment_pending_status = "pending"; const payment_confirmed_status = "confirmed"; const payment_canceled_status = "canceled"; + +//Delivery types +const delivery_caton = "Delivery carton"; +const pickup_carton = "Pick-up carton"; diff --git a/lib/domain/entities/cargo_type.dart b/lib/domain/entities/cargo_type.dart index 0e165ea..a4c0de7 100644 --- a/lib/domain/entities/cargo_type.dart +++ b/lib/domain/entities/cargo_type.dart @@ -5,7 +5,7 @@ class CargoType { double weight; bool isChecked; int qty; - bool? isCutomDuty; + bool isCutomDuty; double customDutyFee; double calRate; double calWeight; @@ -21,7 +21,7 @@ class CargoType { this.calRate = 0, this.isChecked = false, this.qty = 0, - this.isCutomDuty, + this.isCutomDuty = false, this.customDutyFee = 0}); factory CargoType.fromMap(Map map, String id) { @@ -33,8 +33,8 @@ class CargoType { calWeight: map['cal_weight']?.toDouble() ?? 0, calRate: map['cal_rate']?.toDouble() ?? 0, isCutomDuty: map['custom_duty'] ?? false, - customDutyFee: (map['custom_duty_fee'] ?? 0).toDouble(), - qty: (map['qty'] ?? 0).toInt()); + customDutyFee: (map['custom_duty_fee'] ?? 0).toDouble()); + //qty: (map['qty'] ?? 0).toInt()); } Map toMap() { diff --git a/lib/domain/entities/package.dart b/lib/domain/entities/package.dart index 1eb7564..eb94e4f 100644 --- a/lib/domain/entities/package.dart +++ b/lib/domain/entities/package.dart @@ -14,6 +14,7 @@ class Package { DateTime? currentStatusDate; List photoUrls; List shipmentHistory; + List cartonIds; String? desc; String? status; @@ -69,7 +70,8 @@ class Package { this.deliveryAddress, this.isChecked = false, this.photoFiles = const [], - this.senderPhoneNumber}); + this.senderPhoneNumber, + this.cartonIds = const []}); factory Package.fromMap(Map map, String docID) { var _currentStatusDate = (map['status_date'] as Timestamp); @@ -82,6 +84,9 @@ class Package { var da = map['delivery_address']; var _da = da != null ? DeliveryAddress.fromMap(da, da["id"]) : null; + List cartonIds = + map['carton_ids'] == null ? [] : List.from(map['carton_ids']); + return Package( id: docID, userID: map['user_id'], @@ -99,7 +104,8 @@ class Package { deliveryAddress: _da, currentStatusDate: _currentStatusDate.toDate().toLocal(), photoUrls: _photoUrls, - shipmentHistory: _shipmentStatus); + shipmentHistory: _shipmentStatus, + cartonIds: cartonIds); } Map toJson() => { diff --git a/lib/pages/carton/cargo_table.dart b/lib/pages/carton/cargo_table.dart index 0639bf4..ea21a8f 100644 --- a/lib/pages/carton/cargo_table.dart +++ b/lib/pages/carton/cargo_table.dart @@ -106,7 +106,7 @@ class _CargoTableState extends State { padding: const EdgeInsets.only(right: 48.0), child: Align( alignment: Alignment.centerRight, - child: Text(total.toStringAsFixed(2), + child: Text(total.toStringAsFixed(2)+" lb", style: TextStyle(fontWeight: FontWeight.bold))), ), ), diff --git a/lib/pages/carton/cargo_widget.dart b/lib/pages/carton/cargo_widget.dart new file mode 100644 index 0000000..aabfe30 --- /dev/null +++ b/lib/pages/carton/cargo_widget.dart @@ -0,0 +1,356 @@ +import 'package:fcs/helpers/theme.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_icons_null_safety/flutter_icons_null_safety.dart'; +import 'package:provider/provider.dart'; + +import '../../domain/entities/cargo_type.dart'; +import '../../domain/entities/user.dart'; +import '../rates/model/shipment_rate_model.dart'; +import '../widgets/continue_button.dart'; +import '../widgets/display_text.dart'; +import '../widgets/local_title.dart'; +import '../widgets/previous_button.dart'; +import 'custom_duty_addition.dart'; + +typedef OnPrevious = Function( + List cargoTypes, List customDuties); +typedef OnContinue = Function( + List cargoTypes, List customDuties); + +class CargoWidget extends StatefulWidget { + final User sender; + final User consignee; + final List cargoTypes; + final List customDuties; + final OnPrevious? onPrevious; + final OnContinue? onContinue; + + const CargoWidget({ + Key? key, + required this.cargoTypes, + required this.customDuties, + this.onPrevious, + this.onContinue, + required this.sender, + required this.consignee, + }) : super(key: key); + + @override + State createState() => _CargoWidgetState(); +} + +class _CargoWidgetState extends State { + List _cargoTypes = []; + List _customDuties = []; + TextEditingController _totalCtl = TextEditingController(); + List _cargoTypeControllers = []; + + @override + void initState() { + _init(); + super.initState(); + } + + _init() { + var model = context.read(); + _cargoTypes = model.rate.cargoTypes.map((e) => e.clone()).toList(); + + if (widget.cargoTypes.isNotEmpty) { + } else { + _cargoTypes.forEach((e) { + var editor = new TextEditingController(); + editor.text = ''; + editor.addListener(inputChangeListener); + _cargoTypeControllers.add(editor); + }); + } + + if (mounted) { + setState(() {}); + } + } + + bool isFieldEmpty(int index) { + return _cargoTypeControllers[index].text.isEmpty; + } + + List getEmptyFields() { + List emptyFields = []; + for (int i = 0; i < _cargoTypeControllers.length; i++) { + if (isFieldEmpty(i)) { + emptyFields.add(i); + } + } + return emptyFields; + } + + inputChangeListener() { + List emptyFields = getEmptyFields(); + print("emptyFields:$emptyFields"); + + // if (emptyFields.isNotEmpty && emptyFields.length == 1) { + // // _cargoTypeControllers[emptyFields.first].text = + // } + + if (emptyFields.isEmpty) { + _cargoTypes.asMap().entries.forEach((e) { + _cargoTypes[e.key].weight = + double.tryParse(_cargoTypeControllers[e.key].text) ?? 0; + }); + double total = _cargoTypes.fold(0, (sum, value) => sum + value.weight); + setState(() { + _totalCtl.text = total.toString(); + }); + } else { + // if (emptyFields.length == 1) { + // print("_totalCtl.text:${_totalCtl.text}"); + + // if (_totalCtl.text.isNotEmpty) { + // double t = double.tryParse(_totalCtl.text) ?? 0; + + // _cargoTypes.asMap().entries.forEach((e) { + // _cargoTypes[e.key].weight = + // double.tryParse(_cargoTypeControllers[e.key].text) ?? 0; + // }); + // double result = + // _cargoTypes.fold(0, (sum, value) => sum + value.weight); + + // double remaining = t - result; + // setState(() { + // _cargoTypeControllers[emptyFields.first].text = + // remaining.toString(); + // }); + // } + // } + } + } + + @override + Widget build(BuildContext context) { + final senderBox = DisplayText( + text: widget.sender.name, + labelTextKey: "box.sender.title", + iconData: MaterialCommunityIcons.account_arrow_right, + subText: Text(widget.sender.fcsID!, + style: TextStyle(fontSize: 13, color: labelColor)), + ); + + final consigneeBox = DisplayText( + text: widget.consignee.name, + labelTextKey: "box.consignee.title", + iconData: MaterialCommunityIcons.account_arrow_left, + subText: Text(widget.consignee.fcsID!, + style: TextStyle(fontSize: 13, color: labelColor)), + ); + + final userRow = Row( + children: [ + Expanded( + child: senderBox, + flex: 2, + ), + Flexible(child: consigneeBox) + ], + ); + + final cargosBox = Wrap( + alignment: WrapAlignment.spaceBetween, + runSpacing: 15, + children: _cargoTypes.asMap().entries.map((e) { + var key = e.key; + var c = e.value; + return SizedBox( + width: MediaQuery.of(context).size.width / 2.3, + child: Row( + children: [ + InkResponse( + radius: 25, + onTap: () { + setState(() { + _cargoTypeControllers[key].clear(); + }); + }, + child: Icon(MaterialIcons.clear)), + const SizedBox(width: 10), + Flexible( + child: inputTextFieldWidget(context, + lableText: c.name ?? "", + controller: _cargoTypeControllers[key] + // onChanged: (newValue) { + // setState(() { + // _cargoTypes[key].weight = double.tryParse(newValue) ?? 0; + // }); + // double total = + // _cargoTypes.fold(0, (sum, value) => sum + value.weight); + // setState(() { + // _totalCtl.text = total.toString(); + // }); + // }, + ), + ), + ], + ), + ); + }).toList()); + + final totalBox = 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)), + const SizedBox(width: 10), + Flexible( + child: inputTextFieldWidget(context, + lableText: "Total", controller: _totalCtl), + ), + ], + )), + ], + ); + + final subchargeItemTitleBox = LocalTitle( + textKey: "box.input_surcharge_item", + trailing: IconButton( + icon: Icon( + Icons.add_circle, + color: primaryColor, + ), + onPressed: () async { + List? customList = await Navigator.push>( + context, + CupertinoPageRoute( + builder: (context) => + CustomDutyAddition(customDuties: _customDuties))); + if (customList == null) return; + + _customDuties = List.from(customList); + + if (mounted) { + setState(() {}); + } + }), + ); + + final subChargeItemsBox = Wrap( + alignment: WrapAlignment.spaceBetween, + runSpacing: 15, + children: _customDuties.asMap().entries.map((e) { + var key = e.key; + var c = e.value; + return SizedBox( + width: MediaQuery.of(context).size.width / 2.3, + child: Row( + children: [ + InkResponse( + radius: 25, + onTap: () { + setState(() { + _customDuties.removeAt(key); + }); + }, + child: Icon(Feather.minus_circle)), + const SizedBox(width: 10), + Flexible( + child: inputTextFieldWidget( + context, + lableText: c.name ?? "", + ), + ), + ], + ), + ); + }).toList()); + + final continueBtn = ContinueButton( + onTap: () { + // if (selectedPackageList.isEmpty || searchResults.isEmpty) { + // showMsgDialog(context, 'Error', "Please select the packages"); + // return false; + // } + + // if (widget.onContinue != null) { + // widget.onContinue!(selectedPackageList); + // } + }, + ); + + final previousBtn = PreviousButton(onTap: () { + if (widget.onPrevious != null) { + widget.onPrevious!(_cargoTypes, _customDuties); + } + }); + + return Column( + children: [ + Expanded( + child: Padding( + padding: EdgeInsets.only(left: 10, right: 10), + child: ListView( + children: [ + const SizedBox(height: 8), + userRow, + LocalTitle(textKey: "box.input_cargo_weight", topPadding: 10), + cargosBox, + const SizedBox(height: 15), + Divider(), + const SizedBox(height: 5), + totalBox, + subchargeItemTitleBox, + subChargeItemsBox, + const SizedBox(height: 30), + ], + ), + ), + ), + 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 inputTextFieldWidget(BuildContext context, + {required String lableText, + TextEditingController? controller, + Function(String)? onChanged}) { + return TextFormField( + controller: controller, + style: textStyle, + cursorColor: primaryColor, + keyboardType: TextInputType.number, + onChanged: onChanged, + decoration: new InputDecoration( + contentPadding: EdgeInsets.all(0), + labelText: lableText, + labelStyle: newLabelStyle(color: Colors.black54, fontSize: 17), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: primaryColor, width: 1.0)), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: primaryColor, width: 1.0)), + disabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: primaryColor, width: 1.0)), + ), + ); + } +} diff --git a/lib/pages/carton/carton_cargo_table.dart b/lib/pages/carton/carton_cargo_table.dart index c6f67cf..db8fe82 100644 --- a/lib/pages/carton/carton_cargo_table.dart +++ b/lib/pages/carton/carton_cargo_table.dart @@ -90,7 +90,7 @@ class _CargoTableState extends State { ), ), DataCell( - c.isCutomDuty! + c.isCutomDuty ? GestureDetector( onTap: () async { String? _t = await showDialog( diff --git a/lib/pages/carton/carton_editor.dart b/lib/pages/carton/carton_editor.dart index 8998c6e..22b178a 100644 --- a/lib/pages/carton/carton_editor.dart +++ b/lib/pages/carton/carton_editor.dart @@ -14,7 +14,9 @@ import 'package:fcs/pages/widgets/progress.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_vector_icons/flutter_vector_icons.dart'; -import 'mix_cation/mix_cartion_editor.dart'; +import '../main/util.dart'; +import 'carton_editor_for_package.dart'; +import 'mix_carton/mix_carton_editor.dart'; import 'carton_row.dart'; class CartonEditor extends StatefulWidget { @@ -26,7 +28,7 @@ class CartonEditor extends StatefulWidget { } class _CartonEditorState extends State { - List cartonTypes = [carton_from_packages, carton_mix_carton]; + List _cartonTypes = [carton_from_packages, carton_mix_carton]; List _cartons = []; bool _isLoading = false; @@ -34,23 +36,26 @@ class _CartonEditorState extends State { int _billToValue = 1; String? _selectedCartonType; - User? consignee; - User? sender; + User? _consignee; + User? _sender; Carton? _carton; @override void initState() { + _init(); super.initState(); + } + _init() { if (widget.carton != null) { _carton = widget.carton; _selectedCartonType = _carton!.cartonType; _isNew = false; - consignee = User( + _consignee = User( id: _carton!.userID, fcsID: _carton!.fcsID, name: _carton!.userName); - sender = User( + _sender = User( id: _carton!.senderID, fcsID: _carton!.senderFCSID, name: _carton!.senderName); @@ -62,6 +67,20 @@ class _CartonEditorState extends State { Carton(cartonNumber: "A177(A)-3#2", cartonWeight: 35.5), Carton(cartonNumber: "A177(A)-3#1", cartonWeight: 25.5) ]; + _sender = User( + name: "ptd-phyo44 kaelone", + fcsID: "FCS-8X6V", + phoneNumber: "+959444444444", + id: "48u_4s-HiQeW-HwSqeRd9TSMWh3mLZfSk5rpaUEh_zw"); + + _consignee = User( + id: "HsIwG88K-0_HSazgEy5QR27kcjkOvfv7_Sr1JP18Q1A", + name: "One One", + phoneNumber: "+959111111111", + fcsID: "FCS-EFRF"); + } + if (mounted) { + setState(() {}); } } @@ -107,7 +126,7 @@ class _CartonEditorState extends State { final cartonTypeBox = LocalRadioButtons( readOnly: !_isNew, - values: cartonTypes, + values: _cartonTypes, selectedValue: _selectedCartonType, callback: (String? v) { setState(() { @@ -125,6 +144,22 @@ class _CartonEditorState extends State { onTap: () { //for packages if (isFromPackages) { + if (_sender == null) { + showMsgDialog( + context, "Error", "Please select sender's FCS ID"); + return; + } + if (_consignee == null) { + showMsgDialog( + context, "Error", "Please select consignee's FCS ID"); + return; + } + + Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => CartonEditorForPackage( + sender: _sender!, consignee: _consignee!))); } // for mix cartion else { @@ -153,25 +188,25 @@ class _CartonEditorState extends State { icon: Icon(Icons.search, color: Colors.black), onPressed: () => searchUser(context, onUserSelect: (u) { setState(() { - this.consignee = u; + this._consignee = u; }); }, popPage: true)), ], ); final consigneefcsIDBox = DisplayText( - text: consignee != null ? consignee!.fcsID : "", + text: _consignee != null ? _consignee!.fcsID : "", labelTextKey: "processing.fcs.id", icon: FcsIDIcon(), ); final consigneePhoneBox = DisplayText( - text: consignee != null ? consignee!.phoneNumber : "", + text: _consignee != null ? _consignee!.phoneNumber : "", labelTextKey: "processing.phone", iconData: MaterialCommunityIcons.phone); final consigneeNameBox = DisplayText( - text: consignee != null ? consignee!.name : "", + text: _consignee != null ? _consignee!.name : "", labelTextKey: "processing.consignee.name", maxLines: 2, iconData: MaterialCommunityIcons.account_arrow_left); @@ -199,25 +234,25 @@ class _CartonEditorState extends State { icon: Icon(Icons.search, color: Colors.black), onPressed: () => searchUser(context, onUserSelect: (u) { setState(() { - this.sender = u; + this._sender = u; }); }, popPage: true)), ], ); final senderIDBox = DisplayText( - text: sender != null ? sender!.fcsID : "", + text: _sender != null ? _sender!.fcsID : "", labelTextKey: "processing.fcs.id", icon: FcsIDIcon()); final senderPhoneBox = DisplayText( - text: sender != null ? sender!.phoneNumber : "", + text: _sender != null ? _sender!.phoneNumber : "", labelTextKey: "processing.phone", iconData: MaterialCommunityIcons.phone, ); final senderNameBox = DisplayText( - text: sender != null ? sender!.name : "", + text: _sender != null ? _sender!.name : "", labelTextKey: "processing.shipper.name", maxLines: 2, iconData: MaterialCommunityIcons.account_arrow_right); diff --git a/lib/pages/carton/carton_editor_for_package.dart b/lib/pages/carton/carton_editor_for_package.dart new file mode 100644 index 0000000..5921cf6 --- /dev/null +++ b/lib/pages/carton/carton_editor_for_package.dart @@ -0,0 +1,239 @@ +// ignore_for_file: deprecated_member_use + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +import '../../../domain/constants.dart'; +import '../../../domain/entities/carton_size.dart'; +import '../../../domain/entities/fcs_shipment.dart'; +import '../../../domain/vo/local_step.dart'; +import '../../../helpers/theme.dart'; +import '../../domain/entities/cargo_type.dart'; +import '../../domain/entities/package.dart'; +import '../../domain/entities/user.dart'; +import '../main/util.dart'; +import '../widgets/local_text.dart'; +import '../widgets/progress.dart'; +import '../widgets/step_widget.dart'; +import 'cargo_widget.dart'; +import 'carton_size_widget.dart'; +import 'model/package_selection_model.dart'; +import 'package_selection_widget.dart'; + +class CartonEditorForPackage extends StatefulWidget { + final User sender; + final User consignee; + const CartonEditorForPackage( + {Key? key, required this.sender, required this.consignee}) + : super(key: key); + + @override + State createState() => _CartonEditorForPackageState(); +} + +class _CartonEditorForPackageState extends State { + var dateFormatter = DateFormat('dd MMM yyyy'); + final NumberFormat numberFormatter = NumberFormat("#,###"); + List steps = [ + LocalStep(lable: 'Size', stepType: StepType.SIZE), + LocalStep(lable: 'Packages', stepType: StepType.PACKAGES), + LocalStep(lable: 'Cargos', stepType: StepType.CARGOS), + LocalStep(lable: 'Submit', stepType: StepType.SUBMIT) + ]; + List _packages = []; + List _cargoTypes = []; + List _customDuties = []; + + int currentStep = 0; + double _length = 0; + double _width = 0; + double _height = 0; + + String _selectedDeliveryType = delivery_caton; + FcsShipment? _shipment; + String _cartonSizeType = standardCarton; + CartonSize? _standardSize; + bool _isLoading = false; + + @override + void initState() { + context.read().clearSelection(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () { + if (currentStep == 0) { + Navigator.of(context).pop(); + } + if (currentStep > 0) { + setState(() { + currentStep -= 1; + }); + } + + return Future.value(false); + }, + child: LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: AppBar( + elevation: 0, + centerTitle: true, + leading: IconButton( + icon: const Icon(CupertinoIcons.back, + color: primaryColor, size: 25), + onPressed: () { + if (currentStep == 0) { + Navigator.of(context).pop(); + } + if (currentStep > 0) { + setState(() { + currentStep -= 1; + }); + } + }, + ), + backgroundColor: Colors.white, + title: LocalText(context, 'boxes.new', + color: primaryColor, fontSize: 20), + ), + body: Column( + children: [ + StepperWidget( + labels: steps.map((e) => e.lable).toList(), + currentStep: currentStep, + eachStepWidth: MediaQuery.of(context).size.width / 4, + onChange: (index) { + if (index > currentStep) { + return; + } + setState(() { + currentStep = index; + }); + }, + ), + getContent(currentStep) + ], + ))), + ); + } + + Widget getContent(int index) { + var step = steps[index]; + if (step.stepType == StepType.SIZE) { + return Expanded( + child: CartonSizeWidget( + deliveryType: _selectedDeliveryType, + sender: widget.sender, + consignee: widget.consignee, + shipment: _shipment, + cartonSizeType: _cartonSizeType, + standardSize: _standardSize, + length: _length, + width: _width, + height: _height, + onPrevious: () { + Navigator.pop(context); + }, + onContinue: (deliveryType, shipment, cartonSizeType, + {standardSize, length, width, height}) { + setState(() { + _selectedDeliveryType = deliveryType; + _shipment = shipment; + _cartonSizeType = cartonSizeType; + _standardSize = standardSize; + _length = length ?? 0; + _width = width ?? 0; + _height = height ?? 0; + currentStep += 1; + }); + }, + )); + } else if (step.stepType == StepType.PACKAGES) { + return Expanded( + child: PackageSelectionWidget( + sender: widget.sender, + consignee: widget.consignee, + shipment: _shipment!, + packages: _packages, + onContinue: (packages) { + setState(() { + _packages = List.from(packages); + currentStep += 1; + }); + }, + onPrevious: (packages) { + setState(() { + _packages = List.from(packages); + currentStep -= 1; + }); + }, + ), + ); + } else if (step.stepType == StepType.CARGOS) { + return Expanded( + child: CargoWidget( + sender: widget.sender, + consignee: widget.consignee, + cargoTypes: _cargoTypes, + customDuties: _customDuties, + onContinue: (cargoTypes, customDuties) { + setState(() { + _cargoTypes = List.from(cargoTypes); + _customDuties = List.from(customDuties); + currentStep += 1; + }); + }, + onPrevious: (cargoTypes, customDuties) { + setState(() { + _cargoTypes = List.from(cargoTypes); + _customDuties = List.from(customDuties); + currentStep -= 1; + }); + }, + ), + ); + } else { + return Expanded( + child: Text("Submit"), + // child: MixCartonSubmit( + // cartonSizeType: _cartonSizeType, + // standardSize: _standardSize, + // length: _length, + // width: _width, + // height: _height, + // shipment: _shipment!, + // cartons: _packages, + // onCreate: () { + // _create(); + // }, + // onPrevious: () { + // setState(() { + // currentStep -= 1; + // }); + // }, + // ), + ); + } + } + + _create() async { + setState(() { + _isLoading = true; + }); + try { + Navigator.pop(context, true); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } +} diff --git a/lib/pages/carton/carton_info.dart b/lib/pages/carton/carton_info.dart index 9812ecb..1dc5c57 100644 --- a/lib/pages/carton/carton_info.dart +++ b/lib/pages/carton/carton_info.dart @@ -1,6 +1,8 @@ import 'package:fcs/domain/constants.dart'; +import 'package:fcs/domain/entities/cargo_type.dart'; import 'package:fcs/domain/entities/carton.dart'; import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/domain/entities/pickup.dart'; import 'package:fcs/domain/vo/delivery_address.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/carton_size/model/carton_size_model.dart'; @@ -15,7 +17,10 @@ import 'package:fcs/pages/widgets/local_app_bar.dart'; import 'package:fcs/pages/widgets/local_radio_buttons.dart'; import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_title.dart'; +import 'package:fcs/pages/widgets/multi_img_controller.dart'; +import 'package:fcs/pages/widgets/multi_img_file.dart'; import 'package:fcs/pages/widgets/progress.dart'; +import 'package:fcs/pages/widgets/status_tree.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_vector_icons/flutter_vector_icons.dart'; @@ -32,8 +37,10 @@ import 'widgets.dart'; final DateFormat dateFormat = DateFormat("d MMM yyyy"); class CartonInfo extends StatefulWidget { - final Carton? box; - CartonInfo({this.box}); + final Package? package; + final Carton? box; + + CartonInfo({this.box,this.package}); @override _CartonInfoState createState() => _CartonInfoState(); @@ -42,7 +49,12 @@ class CartonInfo extends StatefulWidget { class _CartonInfoState extends State { bool _isLoading = false; Carton? _box; + + List? cargoTypes; + List pickups=['203PVH','Fh290','HH211']; + Map cargos={"General":150,"Electronics":15}; DeliveryAddress? _deliveryAddress = new DeliveryAddress(); + MultiImgController multiImgController = MultiImgController(); TextEditingController _widthController = new TextEditingController(); TextEditingController _heightController = new TextEditingController(); TextEditingController _lengthController = new TextEditingController(); @@ -57,11 +69,13 @@ class _CartonInfoState extends State { bool isSmallBag = false; bool isFromCartons = false; bool isEdiable = false; + Package? _package; @override void initState() { super.initState(); _box = widget.box; + initPackage(widget.package); //for shipment weight volumetricRatio = Provider.of(context, listen: false) .rate @@ -74,6 +88,14 @@ class _CartonInfoState extends State { _loadPackages(); _loadMixCartons(); } + initPackage(Package? package) { + + if (package == null) return; + multiImgController.setImageUrls = package.photoUrls; + setState(() { + _package = package; + }); + } _updateBoxData() { _widthController.text = _box!.width.toString(); @@ -176,14 +198,14 @@ class _CartonInfoState extends State { //iconData: Ionicons.ios_airplane, ); final cartonQrBox = DisplayText( - // text: _box!., + // text: _box!., //labelTextKey: "box.number", iconData: AntDesign.qrcode, ); final shipmentBox = DisplayText( text: _box!.fcsShipmentNumber, labelTextKey: "box.fcs_shipment_num", - // iconData: Ionicons.ios_airplane, + // iconData: Ionicons.ios_airplane, ); final deliveryBox = DisplayText( text: "Delivery Carton", @@ -199,8 +221,9 @@ class _CartonInfoState extends State { final customerNameBox = DisplayText( text: _box!.userName == null ? "" : _box!.userName, - text1: _box!.fcsID == null ? "" : _box!.fcsID, + subText: Text(_box!.fcsID ?? "", style: textStyle), labelTextKey: "box.name", + //iconData: Icons.person, ); @@ -211,18 +234,22 @@ class _CartonInfoState extends State { ); final consigneeNameBox = DisplayText( - text: _box!.userName != null ? _box!.userName : "", - text1: _box!.fcsID != null ? _box!.fcsID : "", + text: _box!.senderName != null ? _box!.senderName : "", + subText: Text(_box!.senderFCSID ?? "", style: textStyle), + labelTextKey: "processing.consignee.name", //maxLines: 2, - //iconData: Ionicons.document_text_outline, + // iconData: Ionicons.document_text_outline, ); final consigneeBox = Container( child: Column( - children: [ - consigneefcsIDBox, + children: [ consigneeNameBox, + IconButton(icon:Icon(Ionicons.document_text_outline), + onPressed:() {},), + Text("Bill to",style:TextStyle(color:Color.fromARGB(255, 57, 80, 233))) + ], ), ); @@ -282,11 +309,22 @@ class _CartonInfoState extends State { SizedBox(child: heightBox, width: 80), ], ); - final packageBox = DisplayText( - text: "203FVH", + final packageBox = DisplayText( + //text: "203FVH", labelTextKey: "box.package", - //iconData: AntDesign.CodeSandbox, + ); + final img = MultiImageFile( + enabled: false, + controller: multiImgController, + title: "Receipt File", + ); + final cargoBox = DisplayText( + //text: "203FVH", + labelTextKey: "box.cargo.type", + + ); + final cartonSizeBox = DisplayText( text: _cartonSizeController.text, @@ -353,63 +391,128 @@ class _CartonInfoState extends State { ] : [], ), - body: Padding( + body: Container( padding: const EdgeInsets.all(10.0), - child: ListView(shrinkWrap: true, children: [ + child: Row( children: [ // Container(child: Row(children: [ // Column(children: [ // LocalTitle(textKey: "box.type.title"), // Align( // alignment: Alignment(-0.1,0.1), // child: getCartonNumberStatus(context, _box!)), - + // ],) // ]),), - Padding(padding: EdgeInsets.only(left: 30), - child: - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [Expanded(child: cartonTypeBox, - flex: 1, - ), - Flexible( - child: cartonQrBox, - ), - ],)), - - - Padding(padding: EdgeInsets.only(left: 30), - child: - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [Expanded(child: shipmentBox, - flex: 1, - ), - Flexible( - child: deliveryBox, - ), - ],)), - Padding(padding: EdgeInsets.only(left: 30), - child: - Row( - children: [ - Flexible(child: customerNameBox, - ), - - Flexible( - child: consigneeNameBox, - ), - Flexible(child: - Column( + Padding( + padding: EdgeInsets.only(left: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, children: [ - Icon(Ionicons.document_text_outline), - Text("Bill to",style:TextStyle(color:Colors.blue)) - ] + Expanded( + child: cartonTypeBox, + flex: 1, + ), + Expanded( + child: IconButton( + alignment: Alignment.centerLeft, + iconSize: 30, + icon: const Icon( AntDesign.qrcode), + onPressed: () { + // ... + }, +), + ), + ], )), - ],)), - + + Padding( + padding: EdgeInsets.only(left: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: shipmentBox, + flex: 1, + ), + Flexible( + child: deliveryBox, + ), + ], + )), + Padding( + padding: EdgeInsets.only(left: 10), + child: Row( + children: [ + Flexible( + child: customerNameBox, + ), + Flexible( + child: consigneeNameBox, + ), + Flexible( + child: Column(children: [ + Icon(Ionicons.document_text_outline), + Text("Bill to", style: TextStyle(color: Colors.blue)) + ])), + ], + )), + Padding(padding: EdgeInsets.only(left:10), + child: Flexible(child: packageBox)), + for(int pack=0;pack { // ], // ) // : Container(), - Padding(padding: EdgeInsets.only(left: 30), - child: - packageBox), - isMixBox ? mixTypeBox : Container(), - isMixBox ? LocalTitle(textKey: "box.mix_caton_title") : Container(), - isMixBox - ? Column(children: _getCartons(context, _box!.mixCartons)) - : Container(), - isFromPackages || isSmallBag - ? CartonPackageTable( - packages: _box!.packages, - ) - : Container(), - // isMixBox ? Container() : LocalTitle(textKey: "box.cargo.type"), - isMixBox ? Container() : cargoTableBox, - ...(isFromPackages || isFromCartons - ? [ - LocalTitle(textKey: "box.dimension"), - cartonSizeBox, - dimBox, - ] - : []), - isMixBox - ? Container() - : LocalTitle(textKey: "box.delivery_address"), - isMixBox - ? Container() - : DefaultDeliveryAddress( - deliveryAddress: _deliveryAddress, - labelKey: "box.delivery_address", - ), - SizedBox( - height: 20, - ) + // Padding(padding: EdgeInsets.only(left: 10), + // child: Row(children:[ + + // ListView.builder( + // itemCount: pickups.length, + // itemBuilder: (context, index) { + // return ListTile( + // title: Text(pickups[index]), + // ); + // },) + + // ])), + // // isMixBox ? mixTypeBox : Container(), + // isMixBox ? LocalTitle(textKey: "box.mix_caton_title") : Container(), + // isMixBox + // ? Column(children: _getCartons(context, _box!.mixCartons)) + // : Container(), + // isFromPackages || isSmallBag + // ? CartonPackageTable( + // packages: _box!.packages, + // ) + // : Container(), + // isMixBox ? Container() : LocalTitle(textKey: "box.cargo.type"), + // isMixBox ? Container() : cargoTableBox, + // ...(isFromPackages || isFromCartons + // ? [ + // LocalTitle(textKey: "box.dimension"), + // cartonSizeBox, + // dimBox, + // ] + // : []), + // isMixBox + // ? Container() + // : LocalTitle(textKey: "box.delivery_address"), + // isMixBox + // ? Container() + // : DefaultDeliveryAddress( + // deliveryAddress: _deliveryAddress, + // labelKey: "box.delivery_address", + // ), + // SizedBox( + // height: 20, + // ) ]), ), ), @@ -515,4 +627,18 @@ class _CartonInfoState extends State { }); } } + Widget _packageList(BuildContext context) { + return ListView.builder( + padding: const EdgeInsets.all(8), + itemCount: pickups.length, + itemBuilder: (BuildContext context, int index) { + return Container( + height: 50, + //color: Colors.amber[pickups[index]], + child: Center(child: Text('Entry ${pickups[index]}')), + ); + }, + //separatorBuilder: (BuildContext context, int index) => const Divider(), + ); +} } diff --git a/lib/pages/carton/carton_size_widget.dart b/lib/pages/carton/carton_size_widget.dart new file mode 100644 index 0000000..50da3fe --- /dev/null +++ b/lib/pages/carton/carton_size_widget.dart @@ -0,0 +1,400 @@ +import 'package:fcs/pages/widgets/local_radio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_vector_icons/flutter_vector_icons.dart'; +import 'package:provider/provider.dart'; +import '../../../domain/constants.dart'; +import '../../../domain/entities/carton_size.dart'; +import '../../../domain/entities/fcs_shipment.dart'; +import '../../../helpers/theme.dart'; +import '../../domain/entities/user.dart'; +import '../carton_size/model/carton_size_model.dart'; +import '../fcs_shipment/model/fcs_shipment_model.dart'; +import '../main/util.dart'; +import '../widgets/box_size_picker.dart'; +import '../widgets/continue_button.dart'; +import '../widgets/display_text.dart'; +import '../widgets/local_dropdown.dart'; +import '../widgets/local_radio_buttons.dart'; +import '../widgets/local_text.dart'; +import '../widgets/local_title.dart'; +import '../widgets/previous_button.dart'; + +typedef OnPrevious = Function(); + +typedef OnContinue = Function(String deliveryType,FcsShipment shipment, String cartonSizeType, + {CartonSize? standardSize, double? length, double? width, double? height}); + +class CartonSizeWidget extends StatefulWidget { + final OnPrevious? onPrevious; + final OnContinue? onContinue; + final User sender; + final User consignee; + final String deliveryType; + final FcsShipment? shipment; + final String cartonSizeType; + final CartonSize? standardSize; + final double? length; + final double? width; + final double? height; + + const CartonSizeWidget( + {Key? key, + this.onPrevious, + this.onContinue, + this.shipment, + required this.cartonSizeType, + this.standardSize, + this.length, + this.width, + this.height, + required this.sender, + required this.consignee, + required this.deliveryType}) + : super(key: key); + + @override + State createState() => _CartonSizeWidgetState(); +} + +class _CartonSizeWidgetState extends State { + List _deliveryTypes = [delivery_caton, pickup_carton]; + + FcsShipment? _shipment; + String _cartionSizeType = standardCarton; + + List _shipments = []; + CartonSize? _selectStandardSize; + late String _selectedDeliveryType; + + TextEditingController _widthController = new TextEditingController(); + TextEditingController _heightController = new TextEditingController(); + TextEditingController _lengthController = new TextEditingController(); + + @override + void initState() { + _init(); + super.initState(); + } + + _init() async { + _selectedDeliveryType = widget.deliveryType; + _shipment = widget.shipment; + _cartionSizeType = widget.cartonSizeType; + + List cartonSizes = context.read().cartonSizes; + _selectStandardSize = widget.standardSize ?? cartonSizes.first; + + _lengthController.text = + widget.length == null ? "0" : widget.length.toString(); + _widthController.text = + widget.width == null ? "0" : widget.width.toString(); + _heightController.text = + widget.height == null ? "0" : widget.height.toString(); + + var fcsShipments = + await context.read().getActiveFcsShipments(); + _shipments = fcsShipments; + + if (mounted) { + setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + List cartonSizes = context.watch().cartonSizes; + bool isStandardSize = _cartionSizeType == standardCarton; + bool isCustomSize = _cartionSizeType == customCarton; + bool isNoneDefinedSize = _cartionSizeType == packageCartion; + + final senderBox = DisplayText( + text: widget.sender.name, + labelTextKey: "box.sender.title", + iconData: MaterialCommunityIcons.account_arrow_right, + subText: Text(widget.sender.fcsID!, + style: TextStyle(fontSize: 13, color: labelColor)), + ); + + final consigneeBox = DisplayText( + text: widget.consignee.name, + labelTextKey: "box.consignee.title", + iconData: MaterialCommunityIcons.account_arrow_left, + subText: Text(widget.consignee.fcsID!, + style: TextStyle(fontSize: 13, color: labelColor)), + ); + + final userRow = Row( + children: [ + Expanded( + child: senderBox, + flex: 2, + ), + Flexible(child: consigneeBox) + ], + ); + + final deliveryTypeBox = LocalRadioButtons( + values: _deliveryTypes, + selectedValue: _selectedDeliveryType, + callback: (String? v) { + setState(() { + _selectedDeliveryType = v!; + }); + }); + + final continueBtn = ContinueButton(onTap: () { + double l = double.tryParse(_lengthController.text) ?? 0; + double w = double.tryParse(_widthController.text) ?? 0; + double h = double.tryParse(_heightController.text) ?? 0; + + if (_shipment == null) { + showMsgDialog(context, "Error", "Please select shipment"); + return; + } + + if (isStandardSize && + _selectStandardSize == null && + !isCustomSize && + !isNoneDefinedSize) { + showMsgDialog( + context, "Error", "Please select the standard carton size"); + return; + } + + if (isCustomSize && + !isStandardSize && + !isNoneDefinedSize && + (l == 0 || w == 0 || h == 0)) { + showMsgDialog(context, "Error", "Please add the carton size"); + return; + } + + if (widget.onContinue != null) { + widget.onContinue!(_selectedDeliveryType,_shipment!, _cartionSizeType, + standardSize: _selectStandardSize, length: l, width: w, height: h); + } + }); + + final previousBtn = PreviousButton(onTap: () { + if (widget.onPrevious != null) { + widget.onPrevious!(); + } + }); + + final standardSizeBox = Padding( + padding: const EdgeInsets.only(left: 34.0, top: 8), + child: IgnorePointer( + ignoring: !isStandardSize, + child: DropdownButton( + isDense: true, + value: _selectStandardSize, + style: TextStyle(color: Colors.black, fontSize: 14), + underline: Container(height: 1, color: Colors.grey), + onChanged: (newValue) { + setState(() { + _selectStandardSize = newValue!; + }); + }, + isExpanded: true, + items: + cartonSizes.map>((CartonSize value) { + return DropdownMenuItem( + value: value, + child: Row( + children: [ + Text("${value.name} - ", + style: TextStyle( + color: isStandardSize ? Colors.black : labelColor)), + Text( + "${value.length.toInt()}”x${value.width.toInt()}”x${value.height.toInt()}”", + style: TextStyle(color: labelColor)), + ], + ), + ); + }).toList(), + ), + ), + ); + + final lengthBox = BoxSizePicker( + lableKey: 'box.length', + controller: _lengthController, + enable: isCustomSize); + + final widthBox = BoxSizePicker( + lableKey: 'box.width', + controller: _widthController, + enable: isCustomSize); + + final heightBox = BoxSizePicker( + lableKey: 'box.height', + controller: _heightController, + enable: isCustomSize); + + final customSizeBox = Padding( + padding: const EdgeInsets.only(left: 34.0), + child: Row( + children: [ + Flexible(child: lengthBox), + Flexible(child: widthBox), + Flexible(child: heightBox) + ], + ), + ); + + final cartonSizedBox = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // standard carton size + InkWell( + onTap: () { + setState(() { + _cartionSizeType = standardCarton; + }); + }, + child: Row(children: [ + LocalRadio( + value: standardCarton, + groupValue: _cartionSizeType, + onChanged: (p0) { + setState(() { + _cartionSizeType = standardCarton; + }); + }, + ), + Flexible( + child: Padding( + padding: const EdgeInsets.only(left: 10), + child: LocalText(context, 'box.standard_carton_size', + fontSize: 15, + color: isStandardSize ? primaryColor : labelColor), + ), + ) + ]), + ), + standardSizeBox, + const SizedBox(height: 20), + // custom size + InkWell( + onTap: () { + setState(() { + _cartionSizeType = customCarton; + }); + }, + child: Row(children: [ + LocalRadio( + value: customCarton, + groupValue: _cartionSizeType, + onChanged: (p0) { + setState(() { + _cartionSizeType = customCarton; + }); + }, + ), + Flexible( + child: Padding( + padding: const EdgeInsets.only(left: 10), + child: LocalText(context, 'box.custom_size', + fontSize: 15, + color: isCustomSize ? primaryColor : Colors.black54), + ), + ) + ]), + ), + customSizeBox, + const SizedBox(height: 10), + // not defined size + InkWell( + onTap: () { + setState(() { + _cartionSizeType = packageCartion; + }); + }, + child: Row(children: [ + LocalRadio( + value: packageCartion, + groupValue: _cartionSizeType, + onChanged: (p0) { + setState(() { + _cartionSizeType = packageCartion; + }); + }, + ), + Flexible( + child: Padding( + padding: const EdgeInsets.only(left: 10), + child: LocalText(context, 'box.package_size', + fontSize: 15, + color: isNoneDefinedSize ? primaryColor : Colors.black54), + ), + ) + ]), + ), + Padding( + padding: const EdgeInsets.only(left: 34.0), + child: Text( + "No defined size", + style: TextStyle( + fontSize: 14, + color: isNoneDefinedSize ? Colors.black : labelColor), + ), + ) + ], + ); + + final fcsShipmentsBox = Container( + padding: EdgeInsets.only(top: 10), + child: LocalDropdown( + callback: (v) { + setState(() { + _shipment = v; + }); + }, + labelKey: "box.shipment", + iconData: Ionicons.ios_airplane, + display: (u) => u.shipmentNumber, + selectedValue: _shipment, + values: _shipments, + )); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: ListView( + padding: EdgeInsets.only(left: 10, right: 10), + children: [ + const SizedBox(height: 8), + userRow, + LocalTitle(textKey: "box.select.delivery", topPadding: 10), + const SizedBox(height: 5), + deliveryTypeBox, + const SizedBox(height: 5), + LocalTitle(textKey: "box.select_carton_size"), + const SizedBox(height: 8), + cartonSizedBox, + const SizedBox(height: 8), + LocalTitle(textKey: "box.select_shipment"), + const SizedBox(height: 5), + fcsShipmentsBox, + const SizedBox(height: 30) + ], + )), + Padding( + padding: const EdgeInsets.only(left: 15, right: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + widget.onPrevious == null ? const SizedBox() : previousBtn, + continueBtn + // warehouse != null ? continueBtn : const SizedBox(), + ], + ), + ), + const SizedBox( + height: 20, + ), + ], + ); + } +} diff --git a/lib/pages/carton/custom_duty_addition.dart b/lib/pages/carton/custom_duty_addition.dart new file mode 100644 index 0000000..d224f86 --- /dev/null +++ b/lib/pages/carton/custom_duty_addition.dart @@ -0,0 +1,127 @@ +import 'package:fcs/domain/entities/cargo_type.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/main/util.dart'; +import 'package:fcs/pages/rates/model/shipment_rate_model.dart'; +import 'package:fcs/pages/widgets/local_app_bar.dart'; +import 'package:fcs/pages/widgets/local_title.dart'; +import 'package:fcs/pages/widgets/progress.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../widgets/local_button.dart'; + +class CustomDutyAddition extends StatefulWidget { + final List customDuties; + + const CustomDutyAddition({super.key, required this.customDuties}); + @override + _CustomDutyAdditionState createState() => _CustomDutyAdditionState(); +} + +class _CustomDutyAdditionState extends State { + bool _isLoading = false; + List customDuties = []; + + @override + void initState() { + _init(); + super.initState(); + } + + _init() { + var shipmentRateModel = + Provider.of(context, listen: false); + customDuties = + shipmentRateModel.rate.customDuties.map((e) => e.clone()).toList(); + + for (var p in customDuties) { + if (widget.customDuties.any((e) => e.id == p.id)) { + p.isChecked = true; + } else { + p.isChecked = false; + } + } + + if (mounted) { + setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + List getCargoRowList(List _c) { + return _c.map((c) { + return Container( + child: Container( + padding: + EdgeInsets.only(left: 10.0, right: 5.0, top: 3.0, bottom: 3.0), + child: InkWell( + onTap: () { + setState(() { + c.isChecked = !c.isChecked; + }); + }, + child: Row( + children: [ + Checkbox( + value: c.isChecked, + activeColor: primaryColor, + onChanged: (bool? check) { + setState(() { + c.isChecked = check ?? false; + }); + }), + new Text(c.name ?? '', style: textStyle), + ], + ), + ), + ), + ); + }).toList(); + } + + final saveBtn = Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: LocalButton( + textKey: "box.cargo.select.btn", + callBack: () { + List _cargos = + customDuties.where((c) => c.isChecked).toList(); + if (_cargos.isEmpty) { + showMsgDialog(context, 'Error', "Please select the cargo type"); + return; + } + Navigator.pop(context, _cargos); + }, + ), + ); + + return LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: LocalAppBar( + labelKey: 'box.select.cargo_type', + backgroundColor: Colors.white, + labelColor: primaryColor, + arrowColor: primaryColor), + body: Container( + padding: EdgeInsets.all(10), + child: ListView( + shrinkWrap: true, + children: [ + LocalTitle( + textKey: "box.select.cargo.title", + ), + Column( + children: getCargoRowList(customDuties), + ), + SizedBox(height: 30), + saveBtn, + SizedBox(height: 20), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/carton/mix_cation/carton_selection_result.dart b/lib/pages/carton/mix_carton/carton_selection_result.dart similarity index 98% rename from lib/pages/carton/mix_cation/carton_selection_result.dart rename to lib/pages/carton/mix_carton/carton_selection_result.dart index 356e4fa..6681949 100644 --- a/lib/pages/carton/mix_cation/carton_selection_result.dart +++ b/lib/pages/carton/mix_carton/carton_selection_result.dart @@ -36,7 +36,7 @@ class CartonSelectionResult extends StatelessWidget { var model = context.watch(); List searchResults = model.cartons; - return searchResults.isEmpty + return searchResults.isEmpty && !model.isLoading ? Center( child: LocalText(context, 'box.no_carton', color: Colors.black, fontSize: 15)) diff --git a/lib/pages/carton/mix_cation/carton_selection_widget.dart b/lib/pages/carton/mix_carton/carton_selection_widget.dart similarity index 99% rename from lib/pages/carton/mix_cation/carton_selection_widget.dart rename to lib/pages/carton/mix_carton/carton_selection_widget.dart index 1e71644..6e7c9d8 100644 --- a/lib/pages/carton/mix_cation/carton_selection_widget.dart +++ b/lib/pages/carton/mix_carton/carton_selection_widget.dart @@ -1,6 +1,6 @@ import 'package:fcs/domain/entities/fcs_shipment.dart'; import 'package:fcs/helpers/theme.dart'; -import 'package:fcs/pages/carton/mix_cation/carton_selection_result.dart'; +import 'package:fcs/pages/carton/mix_carton/carton_selection_result.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/pages/carton/mix_cation/mix_cartion_editor.dart b/lib/pages/carton/mix_carton/mix_carton_editor.dart similarity index 80% rename from lib/pages/carton/mix_cation/mix_cartion_editor.dart rename to lib/pages/carton/mix_carton/mix_carton_editor.dart index ce3b03d..463ceaa 100644 --- a/lib/pages/carton/mix_cation/mix_cartion_editor.dart +++ b/lib/pages/carton/mix_carton/mix_carton_editor.dart @@ -11,11 +11,13 @@ import '../../../domain/entities/carton_size.dart'; import '../../../domain/entities/fcs_shipment.dart'; import '../../../domain/vo/local_step.dart'; import '../../../helpers/theme.dart'; +import '../../main/util.dart'; import '../../widgets/local_text.dart'; import '../../widgets/progress.dart'; import '../../widgets/step_widget.dart'; import '../model/carton_selection_model.dart'; import 'carton_selection_widget.dart'; +import 'mix_carton_submit.dart'; import 'type_widget.dart'; class MixCartonEditor extends StatefulWidget { @@ -35,7 +37,7 @@ class _MixCartonEditorState extends State { LocalStep(lable: 'Cartons', stepType: StepType.CARTONS), LocalStep(lable: 'Submit', stepType: StepType.SUBMIT) ]; - List _cartions = []; + List _cartons = []; int currentStep = 0; double _length = 0; @@ -43,7 +45,7 @@ class _MixCartonEditorState extends State { double _height = 0; FcsShipment? _shipment; - String _cartionSizeType = standardCarton; + String _cartonSizeType = standardCarton; CartonSize? _standardSize; bool _isLoading = false; @@ -97,7 +99,7 @@ class _MixCartonEditorState extends State { StepperWidget( labels: steps.map((e) => e.lable).toList(), currentStep: currentStep, - eachStepWidth: 120, + eachStepWidth: MediaQuery.of(context).size.width / 3, onChange: (index) { if (index > currentStep) { return; @@ -119,7 +121,7 @@ class _MixCartonEditorState extends State { return Expanded( child: TypeWidget( shipment: _shipment, - cartonSizeType: _cartionSizeType, + cartonSizeType: _cartonSizeType, standardSize: _standardSize, length: _length, width: _width, @@ -131,7 +133,7 @@ class _MixCartonEditorState extends State { {standardSize, length, width, height}) { setState(() { _shipment = shipment; - _cartionSizeType = cartonSizeType; + _cartonSizeType = cartonSizeType; _standardSize = standardSize; _length = length ?? 0; _width = width ?? 0; @@ -144,16 +146,16 @@ class _MixCartonEditorState extends State { return Expanded( child: CartonSelectionWidget( shipment: _shipment!, - cartons: _cartions, + cartons: _cartons, onContinue: (cartons) { setState(() { - _cartions = List.from(cartons); + _cartons = List.from(cartons); currentStep += 1; }); }, onPrevious: (cartons) { setState(() { - _cartions = List.from(cartons); + _cartons = List.from(cartons); currentStep -= 1; }); }, @@ -161,27 +163,39 @@ class _MixCartonEditorState extends State { ); } else { return Expanded( - child: Text("Submit"), - // child: StockAdjustmentSubmit( - // warehouse: _warehouse?.name, - // products: products, - // onCreate: () { - // if (user != null && user.hasInventoryCreate()) { - // showConfirmDialog(context, 'stock_adjustment_confirm', _create); - // } else { - // showDialog( - // context: context, - // builder: (BuildContext context) => const AuthorizedDialog( - // uiFunction: funcInventoriesCreate)); - // } - // }, - // onPrevious: () { - // setState(() { - // currentStep -= 1; - // }); - // }, - // ), + child: MixCartonSubmit( + cartonSizeType: _cartonSizeType, + standardSize: _standardSize, + length: _length, + width: _width, + height: _height, + shipment: _shipment!, + cartons: _cartons, + onCreate: () { + _create(); + }, + onPrevious: () { + setState(() { + currentStep -= 1; + }); + }, + ), ); } } + + _create() async { + setState(() { + _isLoading = true; + }); + try { + Navigator.pop(context, true); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } } diff --git a/lib/pages/carton/mix_carton/mix_carton_submit.dart b/lib/pages/carton/mix_carton/mix_carton_submit.dart new file mode 100644 index 0000000..7f386ab --- /dev/null +++ b/lib/pages/carton/mix_carton/mix_carton_submit.dart @@ -0,0 +1,289 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_vector_icons/flutter_vector_icons.dart'; +import 'package:intl/intl.dart'; + +import '../../../domain/constants.dart'; +import '../../../domain/entities/cargo_type.dart'; +import '../../../domain/entities/carton.dart'; +import '../../../domain/entities/carton_size.dart'; +import '../../../domain/entities/fcs_shipment.dart'; +import '../../../helpers/theme.dart'; +import '../../widgets/local_text.dart'; +import '../../widgets/previous_button.dart'; +import '../../widgets/submit_text_widget.dart'; +import "package:collection/collection.dart"; + +typedef OnCreateMixCarton = Function(); +typedef OnPrevious = Function(); + +class MixCartonSubmit extends StatefulWidget { + final FcsShipment shipment; + final List cartons; + final String cartonSizeType; + final CartonSize? standardSize; + final double length; + final double width; + final double height; + final OnCreateMixCarton? onCreate; + final OnPrevious? onPrevious; + const MixCartonSubmit( + {Key? key, + this.onCreate, + this.onPrevious, + required this.shipment, + this.cartons = const [], + this.standardSize, + required this.cartonSizeType, + this.length = 0, + this.width = 0, + this.height = 0}) + : super(key: key); + + @override + State createState() => _MixCartonSubmitState(); +} + +class _MixCartonSubmitState extends State { + final NumberFormat numberFormatter = NumberFormat("#,###"); + Map _mapCargosByWeight = {}; + Map _mapCargosByCustomDutyFee = {}; + + @override + void initState() { + _init(); + super.initState(); + } + + _init() { + List _cargoTypes = []; + for (var c in widget.cartons) { + _cargoTypes.addAll(c.cargoTypes); + } + + // get cargos by weight + Map> _cargosByWeight = + groupCargos(_cargoTypes.where((e) => !e.isCutomDuty).toList()); + + _cargosByWeight.forEach((key, value) { + double total = value.fold(0, (sum, item) => sum + item.weight); + _mapCargosByWeight[key] = total; + }); + + // get cargos by custom duty fee + Map> _cargosByCustomDutyFee = + groupCargos(_cargoTypes.where((e) => e.isCutomDuty).toList()); + + _cargosByCustomDutyFee.forEach((key, value) { + double total = value.fold(0, (sum, item) => sum + item.qty); + _mapCargosByCustomDutyFee[key] = total; + }); + + if (mounted) { + setState(() {}); + } + } + + Map> groupCargos(List cargos) { + var groups = groupBy(cargos, (CargoType e) { + String? _categoryName = e.name; + return _categoryName; + }); + + return groups; + } + + @override + Widget build(BuildContext context) { + String? boxDimension = widget.cartonSizeType == standardCarton + ? "${widget.standardSize?.name} - ${widget.standardSize?.length.toInt()}”x${widget.standardSize?.width.toInt()}”x${widget.standardSize?.height.toInt()}”" + : widget.cartonSizeType == customCarton + ? "${widget.length.toInt()}”x${widget.width.toInt()}”x${widget.height.toInt()}”" + : null; + + final cartonType = Padding( + padding: const EdgeInsets.only(top: 10), + child: SubmitTextWidget( + labelKey: 'box.carton.type', + text: 'Mix Carton', + subText: boxDimension, + ), + ); + + final shipmentBox = Padding( + padding: const EdgeInsets.only(top: 10), + child: SubmitTextWidget( + labelKey: 'box.shipment', + text: widget.shipment.shipmentNumber ?? '', + subText: widget.shipment.status ?? "", + ), + ); + + final cartonsBox = Padding( + padding: const EdgeInsets.only(top: 10), + child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + Padding( + padding: const EdgeInsets.only(left: 5, bottom: 5), + child: LocalText(context, 'box.cartion.count', + translationVariables: [widget.cartons.length.toString()], + color: primaryColor, + fontSize: 16, + fontWeight: FontWeight.normal), + ), + Container( + decoration: BoxDecoration( + border: Border.all(color: primaryColor), + borderRadius: BorderRadius.circular(5), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Wrap( + spacing: 15, + children: widget.cartons.map((e) { + return SizedBox( + width: MediaQuery.of(context).size.width / 2.5, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 3), + child: Text( + e.cartonNumber ?? "", + style: TextStyle(color: Colors.black, fontSize: 15), + ), + ), + ); + }).toList()), + ), + ), + ]), + ); + + final cargosBox = Padding( + padding: const EdgeInsets.only(top: 10), + child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + Padding( + padding: const EdgeInsets.only(left: 5, bottom: 5), + child: LocalText(context, 'box.cargo.type', + color: primaryColor, fontSize: 16, fontWeight: FontWeight.normal), + ), + Container( + decoration: BoxDecoration( + border: Border.all(color: primaryColor), + borderRadius: BorderRadius.circular(5), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _mapCargosByWeight.entries.map((e) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 3), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + e.key ?? "", + style: + TextStyle(color: Colors.black, fontSize: 15), + ), + Text("${numberFormatter.format(e.value)} lb", + style: TextStyle( + color: Colors.black, fontSize: 15)) + ], + ), + ); + }).toList()), + const SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _mapCargosByCustomDutyFee.entries.map((e) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 3), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + e.key ?? "", + style: TextStyle(color: labelColor, fontSize: 15), + ), + Text("${numberFormatter.format(e.value)} pc", + style: + TextStyle(color: labelColor, fontSize: 15)) + ], + ), + ); + }).toList()), + ], + ), + ), + ), + ]), + ); + + final createBtn = InkWell( + onTap: () { + if (widget.onCreate != null) { + widget.onCreate!(); + } + }, + child: Container( + alignment: Alignment.bottomRight, + height: 45, + width: 150, + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(5), + ), + child: TextButton( + onPressed: null, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: LocalText(context, 'box.crete.carton', + color: Colors.white, fontSize: 15), + ), + const SizedBox(width: 5), + const Icon( + MaterialCommunityIcons.check_circle_outline, + color: Colors.white, + ), + ], + ), + ), + )); + + final previousBtn = PreviousButton(onTap: () { + if (widget.onPrevious != null) { + widget.onPrevious!(); + } + }); + + return Column( + children: [ + Expanded( + child: ListView( + padding: const EdgeInsets.all(20), + children: [ + cartonType, + const SizedBox(height: 10), + shipmentBox, + const SizedBox(height: 10), + widget.cartons.isNotEmpty ? cartonsBox : const SizedBox(), + const SizedBox(height: 10), + cargosBox, + const SizedBox(height: 20), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(left: 15, right: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [previousBtn, createBtn], + ), + ), + const SizedBox(height: 20) + ], + ); + } +} diff --git a/lib/pages/carton/mix_cation/type_widget.dart b/lib/pages/carton/mix_carton/type_widget.dart similarity index 98% rename from lib/pages/carton/mix_cation/type_widget.dart rename to lib/pages/carton/mix_carton/type_widget.dart index 81cfe2f..ccc2c32 100644 --- a/lib/pages/carton/mix_cation/type_widget.dart +++ b/lib/pages/carton/mix_carton/type_widget.dart @@ -109,7 +109,7 @@ class _TypeWidgetState extends State { !isCustomSize && !isNoneDefinedSize) { showMsgDialog( - context, "Error", "Please select the standard cartion size"); + context, "Error", "Please select the standard carton size"); return; } @@ -117,7 +117,7 @@ class _TypeWidgetState extends State { !isStandardSize && !isNoneDefinedSize && (l == 0 || w == 0 || h == 0)) { - showMsgDialog(context, "Error", "Please add the cartion size"); + showMsgDialog(context, "Error", "Please add the carton size"); return; } diff --git a/lib/pages/carton/model/carton_selection_model.dart b/lib/pages/carton/model/carton_selection_model.dart index 78e364c..84e84be 100644 --- a/lib/pages/carton/model/carton_selection_model.dart +++ b/lib/pages/carton/model/carton_selection_model.dart @@ -15,6 +15,8 @@ class CartonSelectionModel extends BaseModel { bool reachEnd = false; List cartons = []; + bool isLoading = false; + // for default carton DocumentSnapshot? _lastDocument; bool ended = false; @@ -96,10 +98,11 @@ class CartonSelectionModel extends BaseModel { int rowPerPage = 20; try { + isLoading = true; String path = "/$cartons_collection"; Query query = FirebaseFirestore.instance .collection(path) - .where("fcs_shipment_id", isEqualTo: shipmentId) + // .where("fcs_shipment_id", isEqualTo: shipmentId) // .where("status", isEqualTo: carton_processing_status) // .where("carton_type", isEqualTo: carton_mix_box) .where("is_deleted", isEqualTo: false) @@ -131,6 +134,8 @@ class CartonSelectionModel extends BaseModel { notifyListeners(); } catch (e) { log.warning("error:$e"); + } finally { + isLoading = false; } } diff --git a/lib/pages/carton/model/package_selection_model.dart b/lib/pages/carton/model/package_selection_model.dart new file mode 100644 index 0000000..3f7e0d0 --- /dev/null +++ b/lib/pages/carton/model/package_selection_model.dart @@ -0,0 +1,170 @@ +import 'dart:async'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:logging/logging.dart'; + +import '../../../domain/constants.dart'; +import '../../../domain/entities/package.dart'; +import '../../main/model/base_model.dart'; + +class PackageSelectionModel extends BaseModel { + final log = Logger("PackageSelectionModel"); + // for search + String query = ""; + int offset = 0; + bool reachEnd = false; + List packages = []; + + bool isLoading = false; + + // for default package + DocumentSnapshot? _lastDocument; + bool ended = false; + + List selectedPackageList = []; + + Timer? t; + search(String term, + {bool imm = false, + required String shipmentId, + required String senderId, + required String consigneeId}) async { + query = term; + packages.clear(); + offset = 0; + reachEnd = false; + t?.cancel(); + t = Timer(Duration(milliseconds: imm ? 0 : 800), () async { + await loadMoreSearch( + term: term, + shipmentId: shipmentId, + consigneeId: consigneeId, + senderId: senderId); + }); + } + + Future loadMoreSearch( + {required String term, + required String shipmentId, + required String senderId, + required String consigneeId}) async { + if (term == "") { + await _refresh( + shipmentId: shipmentId, senderId: senderId, consigneeId: consigneeId); + return; + } + // int rowPerPage = 21; + // List list = []; + // SearchPara searchPara = SearchPara(filters: [], term: term); + // isLoading = true; + + // var path = + // "/search/$cartons_collection/${searchPara.escapeTerm}/$rowPerPage/$offset/${searchPara.escapeFilters}"; + + // var result = await requestAPI(path, "GET", + // token: await getToken(), url: Config.instance.searchURL); + + // if (result != null) { + // for (var row in result) { + // var item = ArtistExt.fromMapForSearch(row); + // list.add(item); + // } + // } + + // for (var p in list) { + // selectedArtistList.contains(p) + // ? p.isSelected = true + // : p.isSelected = false; + // } + + // artists.addAll(list); + // offset += rowPerPage; + // if (list.length < rowPerPage) { + // reachEnd = true; + // } + notifyListeners(); + } + + addDefaultPackages( + {required String shipmentId, + required String senderId, + required String consigneeId}) async { + packages.clear(); + await _refresh( + shipmentId: shipmentId, senderId: senderId, consigneeId: consigneeId); + } + + selectPackage(Package a) { + if (a.isChecked) { + selectedPackageList.add(a); + } else { + selectedPackageList.remove(a); + } + } + + Future _refresh( + {required String shipmentId, + required String senderId, + required String consigneeId}) async { + packages.clear(); + _lastDocument = null; + ended = false; + await loadMoreData( + shipmentId: shipmentId, senderId: senderId, consigneeId: consigneeId); + notifyListeners(); + } + + Future loadMoreData( + {required String shipmentId, + required String senderId, + required String consigneeId}) async { + int rowPerPage = 20; + + try { + isLoading = true; + String path = "/$packages_collection"; + Query query = FirebaseFirestore.instance + .collection(path) + // .where("fcs_shipment_id", isEqualTo: shipmentId) + // .where("status", isEqualTo: package_processed_status) + .where("user_id", whereIn: [senderId, consigneeId]) + .where("is_deleted", isEqualTo: false) + .orderBy("created_date", descending: true); + + if (_lastDocument != null) { + query = query.startAfterDocument(_lastDocument!); + } + + QuerySnapshot querySnap = await query.limit(rowPerPage).get(); + + if (querySnap.docs.isEmpty) return; + _lastDocument = querySnap.docs[querySnap.docs.length - 1]; + + List list = querySnap.docs.map((documentSnapshot) { + var p = Package.fromMap(documentSnapshot.data() as Map, + documentSnapshot.id); + return p; + }).toList(); + + for (var p in list) { + selectedPackageList.contains(p) + ? p.isChecked = true + : p.isChecked = false; + } + + packages.addAll(list); + if (list.length < rowPerPage) ended = true; + notifyListeners(); + } catch (e) { + log.warning("error:$e"); + } finally { + isLoading = false; + } + } + + clearSelection() { + selectedPackageList.clear(); + packages.clear(); + query = ""; + } +} diff --git a/lib/pages/carton/package_selection_result.dart b/lib/pages/carton/package_selection_result.dart new file mode 100644 index 0000000..1beba25 --- /dev/null +++ b/lib/pages/carton/package_selection_result.dart @@ -0,0 +1,120 @@ +import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +import '../../../helpers/theme.dart'; +import '../../domain/entities/package.dart'; +import 'model/package_selection_model.dart'; + +typedef OnAction = Future Function(); +final NumberFormat numberFormatter = NumberFormat("#,###"); + +class PackageSelectionResult extends StatelessWidget { + final bool isLoadingMore; + final OnAction onLoadMore; + final OnAction onRefresh; + final Function(Package)? onTap; + final ScrollController controller; + + const PackageSelectionResult( + {super.key, + required this.isLoadingMore, + required this.onLoadMore, + required this.onRefresh, + this.onTap, + required this.controller}); + + bool _scrollNotification(ScrollNotification scrollInfo) { + if (!isLoadingMore && + scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) { + onLoadMore(); + } + return true; + } + + @override + Widget build(BuildContext context) { + var model = context.watch(); + List searchResults = model.packages; + + return searchResults.isEmpty && !model.isLoading + ? Center( + child: LocalText(context, 'box.no_package', + color: Colors.black, fontSize: 15)) + : Column(children: [ + Expanded( + child: NotificationListener( + onNotification: _scrollNotification, + child: RefreshIndicator( + color: primaryColor, + onRefresh: () => onRefresh(), + child: ListView.builder( + controller: controller, + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: (context, index) { + Package package = searchResults[index]; + + return Padding( + padding: const EdgeInsets.only(top: 5, bottom: 5), + child: InkWell( + onTap: () { + if (onTap != null) { + onTap!(package); + } + }, + 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: [ + Expanded( + child: new Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0), + child: new Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + new Text(package.trackingID ?? "", + style: new TextStyle( + fontSize: 15.0, + color: Colors.black)), + new Text( + package.cartonIds.isEmpty + ? "-" + : "${numberFormatter.format(package.cartonIds.length)} Boxes", + style: new TextStyle( + fontSize: 15.0, + color: Colors.grey), + ), + ], + ), + ), + ), + package.isChecked + ? Icon(Icons.check, color: primaryColor) + : const SizedBox() + ], + ), + ), + ), + ); + }, + itemCount: searchResults.length)), + )), + Container( + height: isLoadingMore ? 50.0 : 0, + color: Colors.transparent, + child: const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(primaryColor)), + )), + ]); + } +} diff --git a/lib/pages/carton/package_selection_widget.dart b/lib/pages/carton/package_selection_widget.dart new file mode 100644 index 0000000..9926920 --- /dev/null +++ b/lib/pages/carton/package_selection_widget.dart @@ -0,0 +1,289 @@ +import 'package:fcs/domain/entities/fcs_shipment.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:flutter/material.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/barcode_scanner.dart'; +import '../widgets/continue_button.dart'; +import '../widgets/display_text.dart'; +import '../widgets/local_title.dart'; +import '../widgets/previous_button.dart'; +import 'model/carton_selection_model.dart'; +import 'model/package_selection_model.dart'; +import 'package_selection_result.dart'; + +typedef OnPrevious = Function(List packages); +typedef OnContinue = Function(List packages); + +class PackageSelectionWidget extends StatefulWidget { + final User sender; + final User consignee; + final FcsShipment shipment; + final List packages; + final OnPrevious? onPrevious; + final OnContinue? onContinue; + + const PackageSelectionWidget({ + Key? key, + required this.packages, + this.onPrevious, + this.onContinue, + required this.shipment, + required this.sender, + required this.consignee, + }) : super(key: key); + + @override + State createState() => _PackageSelectionWidgetState(); +} + +class _PackageSelectionWidgetState extends State { + final TextEditingController _controller = TextEditingController(); + String _query = ""; + bool _isLoadMore = false; + final _scrollController = ScrollController(); + + @override + void initState() { + _init(); + super.initState(); + } + + _init() { + var searchModel = context.read(); + searchModel.addDefaultPackages( + shipmentId: widget.shipment.id!, + consigneeId: widget.consignee.id!, + senderId: widget.sender.id!); + + _controller.text = searchModel.query; + _query = searchModel.query; + if (mounted) { + setState(() {}); + } + } + + @override + void didUpdateWidget(covariant PackageSelectionWidget oldWidget) { + _init(); + super.didUpdateWidget(oldWidget); + } + + Future _loadMoreData() async { + if (_isLoadMore) return; + var model = context.read(); + if (model.reachEnd || model.ended) return; + setState(() { + _isLoadMore = true; + }); + if (_query != "") { + await model.loadMoreSearch( + term: _query, + shipmentId: widget.shipment.id!, + consigneeId: widget.consignee.id!, + senderId: widget.sender.id!); + } else { + await model.loadMoreData( + shipmentId: widget.shipment.id!, + consigneeId: widget.consignee.id!, + senderId: widget.sender.id!); + } + + setState(() { + _isLoadMore = false; + }); + } + + @override + Widget build(BuildContext context) { + var model = context.watch(); + List searchResults = model.packages; + List selectedPackageList = model.selectedPackageList; + + final senderBox = DisplayText( + text: widget.sender.name, + labelTextKey: "box.sender.title", + iconData: MaterialCommunityIcons.account_arrow_right, + subText: Text(widget.sender.fcsID!, + style: TextStyle(fontSize: 13, color: labelColor)), + ); + + final consigneeBox = DisplayText( + text: widget.consignee.name, + labelTextKey: "box.consignee.title", + iconData: MaterialCommunityIcons.account_arrow_left, + subText: Text(widget.consignee.fcsID!, + style: TextStyle(fontSize: 13, color: labelColor)), + ); + + final userRow = Row( + children: [ + Expanded( + child: senderBox, + flex: 2, + ), + Flexible(child: consigneeBox) + ], + ); + + final continueBtn = ContinueButton( + onTap: () { + if (selectedPackageList.isEmpty || searchResults.isEmpty) { + showMsgDialog(context, 'Error', "Please select the packages"); + return false; + } + + if (widget.onContinue != null) { + widget.onContinue!(selectedPackageList); + } + }, + ); + + final previousBtn = PreviousButton(onTap: () { + if (widget.onPrevious != null) { + widget.onPrevious!(selectedPackageList); + } + }); + + 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), + ), + ); + + return Column( + children: [ + Expanded( + child: Padding( + padding: EdgeInsets.only(left: 10, right: 10), + child: Column( + children: [ + const SizedBox(height: 8), + userRow, + LocalTitle(textKey: "box.select.package", topPadding: 10), + const SizedBox(height: 10), + searchBox, + Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 10), + child: PackageSelectionResult( + controller: _scrollController, + isLoadingMore: _isLoadMore, + onLoadMore: _loadMoreData, + onRefresh: () async { + _init(); + }, + onTap: (a) async { + setState(() { + a.isChecked = !a.isChecked; + }); + context.read().selectPackage(a); + }, + ), + ), + ), + ], + ), + ), + ), + 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) + ], + ); + } + + _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() + .search(_query, imm: imm, shipmentId: widget.shipment.id!); + } catch (e) { + showMsgDialog(context, 'Error', e.toString()); + } + } +} diff --git a/lib/pages/fcs_shipment/fcs_shipment_list.dart b/lib/pages/fcs_shipment/fcs_shipment_list.dart index 513d400..15fe6cd 100644 --- a/lib/pages/fcs_shipment/fcs_shipment_list.dart +++ b/lib/pages/fcs_shipment/fcs_shipment_list.dart @@ -1,7 +1,6 @@ import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/fcs_shipment/model/fcs_shipment_model.dart'; import 'package:fcs/pages/widgets/local_app_bar.dart'; -import 'package:fcs/pages/widgets/local_popup_menu_button.dart'; import 'package:fcs/pages/widgets/local_popupmenu.dart'; import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/progress.dart'; diff --git a/lib/pages/widgets/display_text.dart b/lib/pages/widgets/display_text.dart index 8963ff6..e027e99 100644 --- a/lib/pages/widgets/display_text.dart +++ b/lib/pages/widgets/display_text.dart @@ -6,7 +6,7 @@ import 'package:provider/provider.dart'; class DisplayText extends StatelessWidget { final String? text; - final String? text1; + final Widget? subText; final String? labelTextKey; final IconData? iconData; final int? maxLines; @@ -14,11 +14,10 @@ class DisplayText extends StatelessWidget { final Color? borderColor; final Widget? icon; - const DisplayText({ Key? key, this.text, - this.text1, + this.subText, this.labelTextKey, this.iconData, this.maxLines = 1, @@ -72,12 +71,9 @@ class DisplayText extends StatelessWidget { text!, style: textStyle, ), - text1 == null + subText == null ? Container() - : Text( - text1!, - style: textStyle, - ), + : subText!, ], ), ), diff --git a/lib/pages/widgets/local_title.dart b/lib/pages/widgets/local_title.dart index 2c3ce3c..0a52481 100644 --- a/lib/pages/widgets/local_title.dart +++ b/lib/pages/widgets/local_title.dart @@ -5,10 +5,11 @@ import 'package:flutter/material.dart'; class LocalTitle extends StatelessWidget { final String? textKey; final Widget? trailing; + final double topPadding; final List? translationVariables; const LocalTitle( - {Key? key, this.textKey, this.trailing, this.translationVariables}) + {Key? key, this.textKey, this.trailing, this.translationVariables,this.topPadding = 18}) : super(key: key); @override @@ -17,7 +18,7 @@ class LocalTitle extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: EdgeInsets.only(top: 18), + padding: EdgeInsets.only(top: topPadding), child: Row( children: [ LocalText(context, textKey!, diff --git a/lib/pages/widgets/submit_text_widget.dart b/lib/pages/widgets/submit_text_widget.dart new file mode 100644 index 0000000..db9f4a7 --- /dev/null +++ b/lib/pages/widgets/submit_text_widget.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import '../../helpers/theme.dart'; +import 'local_text.dart'; + +class SubmitTextWidget extends StatelessWidget { + final String labelKey; + final String text; + final String? subText; + + const SubmitTextWidget( + {Key? key, required this.labelKey, required this.text, this.subText}) + : super(key: key); + @override + Widget build(BuildContext context) { + return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + Padding( + padding: const EdgeInsets.only(left: 5, bottom: 5), + child: LocalText(context, labelKey, + color: primaryColor, fontSize: 16, fontWeight: FontWeight.normal), + ), + Container( + decoration: BoxDecoration( + border: Border.all(color: primaryColor), + borderRadius: BorderRadius.circular(5), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(text, + style: const TextStyle( + fontSize: 16.0, fontWeight: FontWeight.w500)), + subText == null + ? const SizedBox() + : Text(subText!, + style: + const TextStyle(fontSize: 14.0, color: Colors.grey)), + ], + ), + ), + ) + ]); + } +}