diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index 1432a94..537ccb5 100644 --- a/assets/local/localization_en.json +++ b/assets/local/localization_en.json @@ -346,5 +346,20 @@ "receiving.name":"Customer Name", "receiving.phone":"Phone Number", "receiving.create_btn":"Complete receiving", - "Receiving End ================================================================":"" + "Receiving End ================================================================":"", + + "Processing Start ================================================================":"", + "processing.title":"Processing", + "processing.info.title":"Package", + "processing.tracking.id":"Tracking ID", + "processing.name":"Customer Name", + "processing.market":"Market", + "processing.status":"Status", + "processing.desc":"Description", + "processing.remark":"Remark", + "processing.edit.title":"Edit Package", + "processing.delete.confirm":"Delete this package?", + "processing.edit.sub_title":"Processing", + "processing.edit.complete.btn":"Complete processing", + "Processing End ================================================================":"" } \ No newline at end of file diff --git a/assets/local/localization_mu.json b/assets/local/localization_mu.json index eeb4665..7b64f6f 100644 --- a/assets/local/localization_mu.json +++ b/assets/local/localization_mu.json @@ -346,5 +346,20 @@ "receiving.name":"နာမည်", "receiving.phone":"ဖုန်းနံပါတ်", "receiving.create_btn":"လက်ခံမည်", - "Receiving End ================================================================":"" + "Receiving End ================================================================":"", + + "Processing Start ================================================================":"", + "processing.title":"မွမ်းမံခြင်းများ", + "processing.info.title":"အထုပ်", + "processing.tracking.id":"Tracking ID", + "processing.name":"နာမည်", + "processing.market":"အွန်လိုင်စျေးဆိုင်", + "processing.status":"အခြေအနေ", + "processing.desc":"ဖော်ပြချက်", + "processing.remark":"မှတ်ချက်", + "processing.edit.title":"အထုပ် ပြင်ဆင်ခြင်း", + "processing.delete.confirm":"အထုပ်ကို ဖျက်မလား?", + "processing.edit.sub_title":"မွမ်းမံခြင်း", + "processing.edit.complete.btn":"မွမ်းမံခြင်း ပြီးဆုံးသည်", + "Processing End ================================================================":"" } \ No newline at end of file diff --git a/lib/pages/main/home_page.dart b/lib/pages/main/home_page.dart index e0e1bab..4c5aad8 100644 --- a/lib/pages/main/home_page.dart +++ b/lib/pages/main/home_page.dart @@ -18,6 +18,7 @@ import 'package:fcs/pages/invoice/invoce_list.dart'; import 'package:fcs/pages/main/model/language_model.dart'; import 'package:fcs/pages/main/model/main_model.dart'; import 'package:fcs/pages/package/package_list.dart'; +import 'package:fcs/pages/processing/processing_list.dart'; import 'package:fcs/pages/rates/shipment_rates.dart'; import 'package:fcs/pages/receiving/receiving_list.dart'; import 'package:fcs/pages/shipment/shipment_list.dart'; @@ -209,6 +210,10 @@ class _HomePageState extends State { btnCallback: () => Navigator.of(context).push( CupertinoPageRoute(builder: (context) => ReceivingList()))); + final processingBtn = TaskButton("processing.title", + icon: Octicons.package, + btnCallback: () => Navigator.of(context).push( + CupertinoPageRoute(builder: (context) => ProcessingList()))); final boxesBtn = TaskButton("boxes.name", icon: MaterialCommunityIcons.package, btnCallback: () => @@ -290,6 +295,7 @@ class _HomePageState extends State { widgets.add(shipmentCostBtn); user.hasPackages() ? widgets.add(packagesBtn) : ""; user.hasPackages() ? widgets.add(receivingBtn) : ""; + user.hasPackages() ? widgets.add(processingBtn) : ""; true ? widgets.add(boxesBtn) : ""; true ? widgets.add(deliveryBtn) : ""; user.hasCustomers() ? widgets.add(customersBtn) : ""; diff --git a/lib/pages/processing/processing_editor.dart b/lib/pages/processing/processing_editor.dart new file mode 100644 index 0000000..8cab244 --- /dev/null +++ b/lib/pages/processing/processing_editor.dart @@ -0,0 +1,264 @@ +import 'package:fcs/domain/entities/market.dart'; +import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/market/market_editor.dart'; +import 'package:fcs/pages/market/model/market_model.dart'; +import 'package:fcs/pages/package/model/package_model.dart'; +import 'package:fcs/pages/package/tracking_id_page.dart'; +import 'package:fcs/pages/main/util.dart'; +import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; +import 'package:fcs/pages/widgets/display_text.dart'; +import 'package:fcs/pages/widgets/input_text.dart'; +import 'package:fcs/pages/widgets/local_text.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:flutter/material.dart'; +import 'package:flutter_icons/flutter_icons.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class ProcessingEditor extends StatefulWidget { + final Package package; + ProcessingEditor({this.package}); + + @override + _ProcessingEditorState createState() => _ProcessingEditorState(); +} + +class _ProcessingEditorState extends State { + TextEditingController _remarkCtl = new TextEditingController(); + TextEditingController _descCtl = new TextEditingController(); + + Package _package; + bool _isLoading = false; + + @override + void initState() { + super.initState(); + _package = widget.package; + selectedMarket = _package.market ?? ""; + _descCtl.text = _package.desc; + _remarkCtl.text = _package.remark; + multiImgController.setImageUrls = _package.photoUrls; + } + + final DateFormat dateFormat = DateFormat("d MMM yyyy"); + + bool isNew = false; + MultiImgController multiImgController = MultiImgController(); + + @override + Widget build(BuildContext context) { + final trackingIdBox = DisplayText( + text: _package.trackingID, + labelTextKey: "processing.tracking.id", + iconData: MaterialCommunityIcons.barcode_scan, + ); + final statusBox = DisplayText( + text: _package.currentStatus, + labelTextKey: "processing.status", + iconData: AntDesign.exclamationcircleo, + ); + final customerNameBox = DisplayText( + text: _package.userName, + labelTextKey: "processing.name", + iconData: Icons.perm_identity, + ); + final completeProcessingBtn = fcsButton( + context, + getLocalString(context, 'processing.edit.complete.btn'), + callack: _completeProcessing, + ); + final descBox = InputText( + labelTextKey: 'processing.desc', + iconData: MaterialCommunityIcons.message_text_outline, + controller: _descCtl); + final remarkBox = InputText( + labelTextKey: 'processing.remark', + iconData: Entypo.new_message, + controller: _remarkCtl); + + final img = MultiImageFile( + enabled: true, + controller: multiImgController, + title: "Receipt File", + ); + return LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: AppBar( + centerTitle: true, + leading: new IconButton( + icon: new Icon(Icons.close, color: primaryColor, size: 30), + onPressed: () => Navigator.of(context).pop(), + ), + shadowColor: Colors.transparent, + backgroundColor: Colors.white, + title: LocalText( + context, + "processing.edit.title", + fontSize: 20, + color: primaryColor, + ), + actions: [ + IconButton( + icon: Icon(Icons.delete, color: primaryColor), + onPressed: _delete, + ) + ], + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + children: [ + trackingIdBox, + customerNameBox, + statusBox, + Divider(), + Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: LocalText( + context, + "processing.edit.sub_title", + color: primaryColor, + fontSize: 16, + fontWeight: FontWeight.w700, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 18.0, right: 10), + child: Column( + children: [ + marketDropdown(), + descBox, + remarkBox, + img, + ], + ), + ), + completeProcessingBtn, + SizedBox( + height: 20, + ) + ], + ), + ), + ), + ); + } + + String selectedMarket; + Widget marketDropdown() { + List _markets = Provider.of(context).markets; + List markets = _markets.map((e) => e.name).toList(); + markets.insert(0, MANAGE_MARKET); + if (!markets.contains(selectedMarket)) { + markets.insert(0, selectedMarket); + } + + return Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 18.0), + child: LocalText( + context, + "processing.market", + color: primaryColor, + fontSize: 16, + ), + ), + Container( + width: 150, + child: DropdownButton( + value: selectedMarket, + style: TextStyle(color: Colors.black, fontSize: 14), + underline: Container( + height: 1, + color: Colors.grey, + ), + onChanged: (String newValue) { + setState(() { + if (newValue == MANAGE_MARKET) { + selectedMarket = null; + _manageMarket(); + return; + } + selectedMarket = newValue; + }); + }, + isExpanded: true, + items: markets.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value ?? "", + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: value == MANAGE_MARKET + ? secondaryColor + : primaryColor)), + ); + }).toList(), + ), + ), + ], + ); + } + + _manageMarket() { + Navigator.push( + context, + BottomUpPageRoute(MarketEditor()), + ); + } + + _completeProcessing() async { + if (_descCtl.text == "") { + showMsgDialog(context, "Error", "Expected some description"); + return; + } + setState(() { + _isLoading = true; + }); + PackageModel packageModel = + Provider.of(context, listen: false); + try { + _package.desc = _descCtl.text; + _package.remark = _remarkCtl.text; + _package.market = selectedMarket; + await packageModel.completeProcessing(_package, + multiImgController.getAddedFile, multiImgController.getDeletedUrl); + Navigator.pop(context); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } + + _delete() { + showConfirmDialog(context, "processing.delete.confirm", _deletePackage); + } + + _deletePackage() async { + setState(() { + _isLoading = true; + }); + PackageModel packageModel = + Provider.of(context, listen: false); + try { + await packageModel.deletePackage(_package); + Navigator.pop(context, true); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } +} diff --git a/lib/pages/processing/processing_info.dart b/lib/pages/processing/processing_info.dart new file mode 100644 index 0000000..38e740a --- /dev/null +++ b/lib/pages/processing/processing_info.dart @@ -0,0 +1,210 @@ +import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/main/model/main_model.dart'; +import 'package:fcs/pages/package/model/package_model.dart'; +import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; +import 'package:fcs/pages/widgets/display_text.dart'; +import 'package:fcs/pages/widgets/local_text.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:flutter/material.dart'; +import 'package:flutter_icons/flutter_icons.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:timeline_list/timeline.dart'; +import 'package:timeline_list/timeline_model.dart'; + +import 'processing_editor.dart'; + +final DateFormat dateFormat = DateFormat("d MMM yyyy"); + +class ProcessingInfo extends StatefulWidget { + final Package package; + ProcessingInfo({this.package}); + + @override + _ProcessingInfoState createState() => _ProcessingInfoState(); +} + +class _ProcessingInfoState extends State { + var dateFormatter = new DateFormat('dd MMM yyyy'); + Package _package; + bool _isLoading = false; + MultiImgController multiImgController = MultiImgController(); + + @override + void initState() { + super.initState(); + initPackage(widget.package); + } + + initPackage(Package package) { + setState(() { + _package = package; + multiImgController.setImageUrls = package.photoUrls; + }); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + bool isCustomer = Provider.of(context).isCustomer(); + + final trackingIdBox = DisplayText( + text: _package.trackingID, + labelTextKey: "processing.tracking.id", + iconData: MaterialCommunityIcons.barcode_scan, + ); + final customerNameBox = DisplayText( + text: _package.userName, + labelTextKey: "processing.name", + iconData: Icons.perm_identity, + ); + final statusBox = DisplayText( + text: _package.currentStatus, + labelTextKey: "processing.status", + iconData: AntDesign.exclamationcircleo, + ); + final marketBox = DisplayText( + text: _package.market ?? "-", + labelTextKey: "processing.market", + iconData: Icons.store, + ); + final descBox = DisplayText( + text: _package.desc ?? "-", + labelTextKey: "processing.desc", + iconData: MaterialCommunityIcons.message_text_outline, + ); + final remarkBox = DisplayText( + text: _package.remark ?? "-", + labelTextKey: "processing.remark", + iconData: Entypo.new_message, + ); + final img = MultiImageFile( + enabled: false, + controller: multiImgController, + title: "Receipt File", + ); + + return LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: AppBar( + centerTitle: true, + leading: new IconButton( + icon: new Icon(Icons.close, color: primaryColor, size: 30), + onPressed: () => Navigator.of(context).pop(), + ), + shadowColor: Colors.transparent, + backgroundColor: Colors.white, + title: LocalText( + context, + "processing.info.title", + fontSize: 20, + color: primaryColor, + ), + actions: [ + isCustomer + ? Container() + : IconButton( + icon: Icon(Icons.edit, color: primaryColor), + onPressed: _gotoEditor, + ) + ], + ), + body: Card( + child: Column( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(10.0), + child: ListView(children: [ + trackingIdBox, + customerNameBox, + marketBox, + statusBox, + _package.photoUrls.length == 0 ? Container() : img, + descBox, + remarkBox, + ExpansionTile( + initiallyExpanded: true, + title: Text( + 'Status', + style: TextStyle( + color: primaryColor, fontWeight: FontWeight.bold), + ), + children: [ + Container( + padding: EdgeInsets.only(left: 20), + height: 400, + child: Timeline( + children: _models(), + position: TimelinePosition.Left), + ), + ], + ), + SizedBox( + height: 20, + ) + ]), + )), + ], + ), + ), + ), + ); + } + + List _models() { + if (_package.shipmentHistory == null) return []; + return _package.shipmentHistory + .map((e) => TimelineModel( + Padding( + padding: const EdgeInsets.all(18.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(e.status, + style: TextStyle( + color: e.done ? primaryColor : Colors.grey, + fontSize: 16, + fontWeight: FontWeight.bold)), + Text(dateFormat.format(e.date)), + ], + ), + ), + iconBackground: e.done ? primaryColor : Colors.grey, + icon: Icon( + e.status == "shipped" + ? Ionicons.ios_airplane + : e.status == "delivered" + ? MaterialCommunityIcons.truck_fast + : e.status == "processed" + ? MaterialIcons.check + : Octicons.package, + color: Colors.white, + ))) + .toList(); + } + + _gotoEditor() async { + bool deleted = await Navigator.push( + context, + BottomUpPageRoute(ProcessingEditor( + package: widget.package, + ))); + if (deleted ?? false) { + Navigator.pop(context); + } else { + PackageModel packageModel = + Provider.of(context, listen: false); + Package p = await packageModel.getPackage(_package.id); + initPackage(p); + } + } +} diff --git a/lib/pages/processing/processing_list.dart b/lib/pages/processing/processing_list.dart new file mode 100644 index 0000000..6e34748 --- /dev/null +++ b/lib/pages/processing/processing_list.dart @@ -0,0 +1,97 @@ +import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/localization/app_translations.dart'; +import 'package:fcs/pages/main/model/main_model.dart'; +import 'package:fcs/pages/package/model/package_model.dart'; +import 'package:fcs/pages/package/package_info.dart'; +import 'package:fcs/pages/package/package_new.dart'; +import 'package:fcs/pages/package_search/package_serach.dart'; +import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; +import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:fcs/pages/widgets/progress.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'processing_list_row.dart'; + +class ProcessingList extends StatefulWidget { + @override + _ProcessingListState createState() => _ProcessingListState(); +} + +class _ProcessingListState extends State { + bool _isLoading = false; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var packageModel = Provider.of(context); + bool isCustomer = context.select((MainModel m) => m.isCustomer()); + + return LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: AppBar( + centerTitle: true, + leading: new IconButton( + icon: new Icon(CupertinoIcons.back), + onPressed: () => Navigator.of(context).pop(), + ), + backgroundColor: primaryColor, + title: LocalText( + context, + "processing.title", + fontSize: 20, + color: Colors.white, + ), + actions: [ + isCustomer + ? Container() + : IconButton( + icon: Icon( + Icons.search, + color: Colors.white, + ), + iconSize: 30, + onPressed: () => searchPackage(context, + callbackPackageSelect: _searchCallback), + ), + ], + ), + body: new ListView.separated( + separatorBuilder: (context, index) => Divider( + color: Colors.black, + ), + scrollDirection: Axis.vertical, + padding: EdgeInsets.only(top: 15), + shrinkWrap: true, + itemCount: packageModel.packages.length, + itemBuilder: (BuildContext context, int index) { + return ProcessingListRow( + key: ValueKey(packageModel.packages[index].id), + package: packageModel.packages[index], + ); + })), + ); + } + + _searchCallback(Package package) async { + var packageModel = Provider.of(context, listen: false); + Package _package = await packageModel.getPackage(package.id); + if (_package == null) return; + Navigator.push( + context, + BottomUpPageRoute(PackageInfo(package: _package)), + ); + } +} diff --git a/lib/pages/processing/processing_list_row.dart b/lib/pages/processing/processing_list_row.dart new file mode 100644 index 0000000..7a1f299 --- /dev/null +++ b/lib/pages/processing/processing_list_row.dart @@ -0,0 +1,90 @@ +import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/pages/package/package_info.dart'; +import 'package:fcs/pages/main/util.dart'; +import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +import 'processing_info.dart'; + +typedef CallbackPackageSelect(Package package); + +class ProcessingListRow extends StatelessWidget { + final Package package; + final CallbackPackageSelect callbackPackageSelect; + final double dotSize = 15.0; + final DateFormat dateFormat = new DateFormat("dd MMM yyyy"); + + ProcessingListRow({Key key, this.package, this.callbackPackageSelect}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.only(left: 15, right: 15), + child: InkWell( + onTap: () { + if (callbackPackageSelect != null) { + callbackPackageSelect(package); + return; + } + Navigator.push( + context, + BottomUpPageRoute(ProcessingInfo(package: package)), + ); + }, + child: Row( + children: [ + Expanded( + child: new Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: new Row( + children: [ + new Expanded( + child: new Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: new Text( + package.id == null ? '' : package.trackingID, + style: new TextStyle( + fontSize: 15.0, color: Colors.black), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: new Text( + package.market == null ? '' : package.market, + style: new TextStyle( + fontSize: 15.0, color: Colors.black), + ), + ), + ], + ), + ), + ], + ), + ), + ), + Column( + children: [ + Padding( + padding: const EdgeInsets.all(3.0), + child: getStatus(package.currentStatus), + ), + Padding( + padding: const EdgeInsets.all(0), + child: new Text( + dateFormat.format(package.currentStatusDate), + style: new TextStyle(fontSize: 15.0, color: Colors.grey), + ), + ), + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/staff/staff_editor.dart b/lib/pages/staff/staff_editor.dart index cb48073..5c2af22 100644 --- a/lib/pages/staff/staff_editor.dart +++ b/lib/pages/staff/staff_editor.dart @@ -128,7 +128,7 @@ class _StaffEditorState extends State { Widget build(BuildContext context) { final namebox = DisplayText( text: user.name, - labelTextKey: getLocalString(context, "customer.name"), + labelTextKey: "customer.name", iconData: Icons.person, ); var phoneNumberBox = Row( @@ -136,7 +136,7 @@ class _StaffEditorState extends State { Expanded( child: DisplayText( text: user.phoneNumber, - labelTextKey: getLocalString(context, "customer.phone"), + labelTextKey: "customer.phone", iconData: Icons.phone, )), isNew