Merge branch 'master' of tzw/fcs into master

This commit is contained in:
2024-02-15 08:34:58 +06:30
committed by Gogs
24 changed files with 1011 additions and 165 deletions

View File

@@ -31,6 +31,9 @@
"Welcome Start ================================================================":"",
"welcome.signin":"Sign In",
"welcome.msg":"Welcome to FCS!",
"welcome.pinlogin":"Login",
"welcome.pinlogin.fcsid":"Enter FCS ID",
"welcome.pinlogin.pin":"Enter Your PIN",
"Welcome End ================================================================":"",
"Contact Start ================================================================":"",
@@ -151,6 +154,14 @@
"staff.add":"Add",
"staff.update":"Update",
"staff.phone.search":"Enter phone number",
"staff.pin_login":"PIN Login",
"staff.edit_pin":"Edit PIN",
"staff.edit_privileges":"Edit Privileges",
"staff.privilege.title":"Privileges",
"staff.pin.title":"PIN",
"staff.new_pin":"New PIN",
"staff.confirm_pin":"Confirm PIN",
"staff.enable_pin":"Enable PIN Login",
"Staff End ================================================================":"",
"Profile Start ================================================================":"",

View File

@@ -29,6 +29,9 @@
"Welcome Start ================================================================":"",
"welcome.signin":"ဝင်မည်",
"welcome.pinlogin":"ဝင်ရောက်ပါ",
"welcome.pinlogin.fcsid":"FCS ID ကိုရိုက်ထည့်ပါ",
"welcome.pinlogin.pin":"သင်၏ pin ကိုထည့်ပါ",
"welcome.msg":"FCS က ကြိုဆိုပါတယ်!",
"Welcome End ================================================================":"",
@@ -133,6 +136,7 @@
"customer.disable.btn":"ပိတ်ပါ",
"customer.enable.btn":"ဖွင့်ပါ",
"customer.chat.btn":"Chat",
"Customer End ================================================================":"",
"Invitation Start ================================================================":"",
@@ -151,6 +155,14 @@
"staff.add":"အသစ်ထည့်မည်",
"staff.update":"ပြုပြင်မည်",
"staff.phone.search":"ဖုန်းနံပါတ် ရိုက်ထည့်ပါ",
"staff.pin_login":"PIN Login",
"staff.edit_pin":"Edit PIN",
"staff.edit_privileges":"Edit Privileges",
"staff.privilege.title":"Privileges",
"staff.pin.title":"PIN",
"staff.new_pin":"New PIN",
"staff.confirm_pin":"Confirm PIN",
"staff.enable_pin":"Enable PIN Login",
"Staff End ================================================================":"",
"Profile Start ================================================================":"",

View File

@@ -34,6 +34,7 @@ const pkg_files_path = "/packages";
const shipment_labels_files_path = "/shipment_labels";
const receipt_labels_files_path = "/receipts";
const pickups_files_path = "/pickups";
const carton_files_path = "/cartons";
// Link page
const page_payment_methods = "payment_methods";

View File

@@ -47,7 +47,6 @@ class Carton {
int weight;
String? packageType;
String? pickUpID;
List<String> photos = [];
List<String> photoUrls;
String? remark;
DateTime? arrivedDate;

View File

@@ -19,6 +19,9 @@ class User {
int userUnseenCount;
int fcsUnseenCount;
String? preferCurrency;
bool enablePinLogin;
String? pinDigit;
String? confirmPinDigit;
String get initial =>
name != null && name != "" ? name!.substring(0, 1) : "?";
@@ -68,7 +71,10 @@ class User {
this.lastMessageTime,
this.userUnseenCount = 0,
this.fcsUnseenCount = 0,
this.preferCurrency});
this.preferCurrency,
this.enablePinLogin = false,
this.pinDigit,
this.confirmPinDigit});
factory User.fromJson(Map<String, dynamic> json) {
return User(

View File

@@ -4,29 +4,29 @@ import 'package:fcs/pages/widgets/local_app_bar.dart';
import 'package:fcs/pages/widgets/multi_img_controller.dart';
import 'package:fcs/pages/widgets/progress.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../main/util.dart';
import '../widgets/local_button.dart';
import '../widgets/multi_img_file.dart';
typedef void FindCallBack();
import 'model/carton_model.dart';
class CartonImageUploadEditor extends StatefulWidget {
final Carton? box;
const CartonImageUploadEditor({this.box});
final Carton carton;
const CartonImageUploadEditor({required this.carton});
@override
_CartonImageUploaState createState() => _CartonImageUploaState();
}
class _CartonImageUploaState extends State<CartonImageUploadEditor> {
bool _isLoading = false;
Carton? _box;
MultiImgController multiImgController = MultiImgController();
MultiImgController _multiImgController = MultiImgController();
@override
void initState() {
super.initState();
_box = widget.box;
multiImgController.setImageUrls = _box?.photoUrls;
_multiImgController.setImageUrls = widget.carton.photoUrls;
}
@override
@@ -35,8 +35,9 @@ class _CartonImageUploaState extends State<CartonImageUploadEditor> {
padding: const EdgeInsets.symmetric(horizontal: 30),
child: LocalButton(
textKey: "btn.save",
callBack: () {},
),
callBack: () {
_uploadImage();
}),
);
return LocalProgress(
@@ -53,19 +54,37 @@ class _CartonImageUploaState extends State<CartonImageUploadEditor> {
child: ListView(
children: [
Center(
child: Text("${_box?.cartonNumber}",
child: Text("${widget.carton.cartonNumber}",
style: TextStyle(
color: primaryColor,
fontSize: 25,
))),
MultiImageFile(
enabled: true,
controller: multiImgController,
controller: _multiImgController,
title: "Receipt File",
),
const SizedBox(height: 20),
saveBtn,
],
),
)));
}
_uploadImage() async {
setState(() {
_isLoading = true;
});
try {
await context.read<CartonModel>().uploadCartonImages(widget.carton,
_multiImgController.getAddedFile, _multiImgController.getDeletedUrl);
Navigator.pop(context, true);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

View File

@@ -34,7 +34,7 @@ class CartonInfo extends StatefulWidget {
class _CartonInfoState extends State<CartonInfo> {
final DateFormat dateFormat = DateFormat("d MMM yyyy");
final NumberFormat numberFormatter = NumberFormat("#,###");
MultiImgController multiImgController = MultiImgController();
MultiImgController _multiImgController = MultiImgController();
bool _isLoading = false;
late Carton _carton;
List<CargoType> _cargoTypes = [];
@@ -58,7 +58,7 @@ class _CartonInfoState extends State<CartonInfo> {
_carton.cartonType = carton_from_packages;
_carton.billTo = billToConsignee;
_carton.cartonSizeType = customCarton;
multiImgController.setImageUrls = _carton.photos;
_multiImgController.setImageUrls = _carton.photoUrls;
_cargoTypes = _carton.cargoTypes.where((e) => !e.isCutomDuty).toList();
_surchareItems = _carton.cargoTypes.where((e) => e.isCutomDuty).toList();
if (_carton.cartonType == carton_from_packages) {
@@ -245,7 +245,6 @@ class _CartonInfoState extends State<CartonInfo> {
style: TextStyle(color: Colors.black54, fontSize: 15)))
],
),
//),
Container(
child: Padding(
padding: const EdgeInsets.only(right: 60),
@@ -273,7 +272,6 @@ class _CartonInfoState extends State<CartonInfo> {
);
}).toList()),
const SizedBox(height: 10),
],
),
),
@@ -283,7 +281,7 @@ class _CartonInfoState extends State<CartonInfo> {
final img = MultiImageFile(
enabled: false,
controller: multiImgController,
controller: _multiImgController,
title: "Receipt File",
);
@@ -293,19 +291,29 @@ class _CartonInfoState extends State<CartonInfo> {
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xff272262),
elevation: 3,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0)),
minimumSize: Size(10, 35),
),
onPressed: () {
Navigator.push(
onPressed: () async {
bool? updated = await Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => CartonImageUploadEditor(box: _carton)),
builder: (context) =>
CartonImageUploadEditor(carton: _carton)),
);
if (updated ?? false) {
Carton? c = await context
.read<CartonModel>()
.getCarton(widget.carton.id ?? "");
if (c == null) return;
_carton = c;
_init();
}
},
child: const Text('Upload Images'),
),
child: LocalText(context, "box.imageupload.title",
color: Colors.white, fontSize: 14)),
);
final deleteBtn = Padding(
@@ -390,7 +398,9 @@ class _CartonInfoState extends State<CartonInfo> {
_surchareItems.isEmpty ? const SizedBox() : surchargeItemBox,
uploadImageBtn,
img,
deleteBtn
const SizedBox(height: 15),
deleteBtn,
const SizedBox(height: 20)
]))));
}

View File

@@ -95,11 +95,18 @@ class _CartonSizeWidgetState extends State<CartonSizeWidget> {
_heightController.text =
widget.height == null ? "0" : removeTrailingZeros(widget.height ?? 0);
_loadShipment();
if (mounted) {
setState(() {});
}
}
_loadShipment() async {
var fcsShipments =
await context.read<FcsShipmentModel>().getActiveFcsShipments();
_shipments = fcsShipments;
_shipment = widget.shipment;
if (mounted) {
setState(() {});
}
@@ -107,7 +114,7 @@ class _CartonSizeWidgetState extends State<CartonSizeWidget> {
@override
void didUpdateWidget(covariant CartonSizeWidget oldWidget) {
_init();
_loadShipment();
super.didUpdateWidget(oldWidget);
}

View File

@@ -76,11 +76,18 @@ class _TypeWidgetState extends State<TypeWidget> {
_heightController.text =
widget.height == null ? "0" : removeTrailingZeros(widget.height ?? 0);
_loadShipment();
if (mounted) {
setState(() {});
}
}
_loadShipment() async {
var fcsShipments =
await context.read<FcsShipmentModel>().getActiveFcsShipments();
_shipments = fcsShipments;
_shipment = widget.shipment;
if (mounted) {
setState(() {});
}
@@ -88,7 +95,7 @@ class _TypeWidgetState extends State<TypeWidget> {
@override
void didUpdateWidget(covariant TypeWidget oldWidget) {
_init();
_loadShipment();
super.didUpdateWidget(oldWidget);
}

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:fcs/data/services/services.dart';
@@ -10,6 +11,8 @@ import 'package:fcs/pagination/paginator_listener.dart';
import 'package:logging/logging.dart';
import '../../../domain/entities/user.dart';
import '../../../helpers/firebase_helper.dart';
import 'package:path/path.dart' as Path;
class CartonModel extends BaseModel {
final log = Logger('CartonModel');
@@ -241,4 +244,40 @@ class CartonModel extends BaseModel {
}
return cartons;
}
Future<void> uploadCartonImages(
Carton carton, List<File?> files, List<String?> deletedUrls) async {
if (deletedUrls.isNotEmpty) {
for (String? url in deletedUrls) {
carton.photoUrls.remove(url);
}
}
List<String> uploadedURL = [];
if (files.isNotEmpty) {
var count =
(carton.photoUrls.length) + files.length - (deletedUrls.length);
if (count > uploadPhotoLimit)
throw Exception("Exceed number of file upload");
carton.photoUrls = carton.photoUrls;
String path = Path.join(carton_files_path);
uploadedURL = await uploadFiles(path, files);
uploadedURL.forEach((url) {
carton.photoUrls.add(url);
});
}
try {
// await Services.instance.packageService.updateReceiving(package);
} catch (e) {
// delete newly uploaded photos if fails
try {
deleteStorageFromUrls(uploadedURL);
carton.photoUrls.removeWhere((i) => uploadedURL.contains(i));
} catch (e) {}
throw e;
}
return deleteStorageFromUrls(deletedUrls);
}
}

View File

@@ -1,7 +1,5 @@
import 'package:fcs/domain/vo/delivery_address.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/widgets/local_text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_vector_icons/flutter_vector_icons.dart';
@@ -26,26 +24,35 @@ class DeliveryAddressRow extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
deliveryAddress.fullName!=""
?
line(context, deliveryAddress.fullName,
iconData: MaterialCommunityIcons.account,
color: primaryColor,
fontSize: 16),
color: Colors.black,
fontSize: 16):SizedBox(),
deliveryAddress.phoneNumber!=""
?
line(context, deliveryAddress.phoneNumber,
iconData: Icons.phone, color: primaryColor, fontSize: 16),
SizedBox(
height: 5,
),
iconData: Icons.phone, color: Colors.black, fontSize: 16):SizedBox(),
deliveryAddress.addressLine1!=""
?
line(context, deliveryAddress.addressLine1,
iconData: Icons.location_on),
iconData: Icons.location_on,color: Colors.black, fontSize: 16):SizedBox(),
deliveryAddress.addressLine2!=""
?
line(
context,
deliveryAddress.addressLine2,
),
deliveryAddress.addressLine2,color: Colors.black, fontSize: 16
):SizedBox(),
deliveryAddress.city!=""
?
line(
context,
deliveryAddress.city,
),
line(context, deliveryAddress.state),
deliveryAddress.city,color: Colors.black, fontSize: 16
):SizedBox(),
deliveryAddress.state!=""
?
line(context, deliveryAddress.state,color: Colors.black, fontSize: 16):SizedBox(),
],
),
),

View File

@@ -1,13 +1,12 @@
import 'package:fcs/domain/entities/discount.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/localization/app_translations.dart';
import 'package:fcs/pages/discount/model/discount_model.dart';
import 'package:fcs/pages/main/util.dart';
import 'package:fcs/pages/user_search/user_search.dart';
import 'package:fcs/pages/widgets/display_text.dart';
import 'package:fcs/pages/widgets/input_text.dart';
import 'package:fcs/pages/widgets/local_app_bar.dart';
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 'package:font_awesome_flutter/font_awesome_flutter.dart';
@@ -88,29 +87,17 @@ class _DiscountEditorState extends State<DiscountEditor> {
inAsyncCall: _isLoading,
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
centerTitle: true,
title: Text(
AppTranslations.of(context)!.text("discount.form"),
),
leading: new IconButton(
icon: new Icon(CupertinoIcons.back),
onPressed: () {
if (isDataChanged()) {
showConfirmDialog(context, "back.button_confirm", () {
Navigator.of(context).pop();
});
} else {
Navigator.of(context).pop();
}
},
),
backgroundColor: primaryColor,
appBar: LocalAppBar(
labelKey: "discount.form",
backgroundColor: Colors.white,
arrowColor: primaryColor,
labelColor: primaryColor,
actions: [
IconButton(
icon: Icon(Icons.delete),
onPressed: _delete,
)
_isNew
? const SizedBox()
: IconButton(
icon: Icon(Icons.delete, color: primaryColor),
onPressed: _delete)
],
),
body: Padding(

View File

@@ -1,7 +1,7 @@
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/localization/app_translations.dart';
import 'package:fcs/pages/discount/discount_list_row.dart';
import 'package:fcs/pages/discount/model/discount_model.dart';
import 'package:fcs/pages/widgets/local_app_bar.dart';
import 'package:fcs/pages/widgets/local_popup_menu_button.dart';
import 'package:fcs/domain/vo/local_popupmenu.dart';
import 'package:fcs/pages/widgets/local_text.dart';
@@ -58,26 +58,13 @@ class _DiscountListState extends State<DiscountList> {
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(
AppTranslations.of(context)!.text("discount.title"),
),
leading: new IconButton(
icon: new Icon(CupertinoIcons.back),
onPressed: () => Navigator.of(context).pop(),
),
backgroundColor: primaryColor,
actions: <Widget>[popupMenu],
),
appBar: LocalAppBar(labelKey: "discount.title", actions: [popupMenu]),
body: Column(
children: [
Expanded(
child: ListView.separated(
separatorBuilder: (context, index) => Divider(
color: Colors.black,
height: 1,
),
separatorBuilder: (context, index) =>
Divider(color: Colors.black, height: 1),
controller: _controller,
itemCount: discountModel.discounts.length,
itemBuilder: (BuildContext context, int index) {

View File

@@ -184,7 +184,7 @@ class _FcsShipmentInfoState extends State<FcsShipmentInfo> {
body: Card(
elevation: 0,
child: Padding(
padding: const EdgeInsets.all(10.0),
padding: const EdgeInsets.only(left: 12.0, right: 12),
child: ListView(children: <Widget>[
statusBox,
Row(mainAxisAlignment: MainAxisAlignment.end, children: [

View File

@@ -6,7 +6,6 @@ import 'package:fcs/domain/entities/package.dart';
import 'package:fcs/domain/entities/user.dart';
import 'package:fcs/helpers/shared_pref.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/localization/transalation.dart';
import 'package:fcs/pages/carton/carton_list.dart';
import 'package:fcs/pages/chat/message_detail.dart';
import 'package:fcs/pages/chat/model/message_model.dart';
@@ -27,6 +26,7 @@ import 'package:fcs/pages/pickup/pickup_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/signin/pinlogin_page.dart';
import 'package:fcs/pages/staff/staff_list.dart';
import 'package:fcs/pages/widgets/badge.dart';
import 'package:fcs/pages/widgets/bottom_widgets.dart';
@@ -357,7 +357,7 @@ class _HomePageState extends State<HomePage> {
selectedColor: Colors.white,
color: Colors.blue,
children: <Widget>[
Icon(MaterialCommunityIcons.account_tie),
Icon(MaterialCommunityIcons.account_tie,size: 25),
],
onPressed: (i) => this.setState(() {
isFcs[0] = !isFcs[0];
@@ -366,25 +366,7 @@ class _HomePageState extends State<HomePage> {
isSelected: isFcs,
selectedBorderColor: Colors.white24,
);
final langToggle = ToggleButtons(
children: <Widget>[
Image.asset(
'icons/flags/png/us.png',
package: 'country_icons',
fit: BoxFit.fitWidth,
width: 25,
),
Image.asset(
'icons/flags/png/mm.png',
package: 'country_icons',
fit: BoxFit.fitWidth,
width: 25,
)
],
onPressed: _langChange,
isSelected: languageModel.currentState,
selectedBorderColor: Colors.white24,
);
final signinBtn = TextButton(
onPressed: () {
Navigator.of(context)
@@ -399,8 +381,23 @@ class _HomePageState extends State<HomePage> {
onPressed: () {
Navigator.of(context).push(RightLeftPageRoute(Profile()));
},
iconSize: 30,
icon: Icon(Icons.account_circle,color: buttonColor,),
iconSize: 25,
icon: Icon(
Icons.account_circle,
color: buttonColor,
),
);
final pinLoginBtn = IconButton(
onPressed: () {
Navigator.of(context)
.push(CupertinoPageRoute(builder: (context) => PinLoginPage()));
},
iconSize: 25,
icon: Icon(
Icons.lock_outline,
color: buttonColor,
),
);
var searchInput = Row(children: [
@@ -478,16 +475,14 @@ class _HomePageState extends State<HomePage> {
actions: login
? user.isCustomer()
? <Widget>[
langToggle,
profileBtn,
]
: <Widget>[
pinLoginBtn,
fcsToggle,
langToggle,
profileBtn,
]
: <Widget>[
langToggle,
signinBtn,
]),
body: Container(
@@ -518,17 +513,6 @@ class _HomePageState extends State<HomePage> {
);
}
_langChange(index) {
var languageModel = Provider.of<LanguageModel>(context, listen: false);
languageModel.saveLanguage(Translation().supportedLanguages[index]);
setState(() {
isSelected.asMap().forEach((i, e) {
isSelected[i] = false;
});
isSelected[index] = !isSelected[index];
});
}
_lookup() async {
setState(() {
_isLoading = true;
@@ -536,7 +520,7 @@ class _HomePageState extends State<HomePage> {
try {
String term = _searchCtl.text;
if ( term.trim() == "") return;
if (term.trim() == "") return;
var packageModel = Provider.of<PackageModel>(context, listen: false);
Package? package = await packageModel.lookupPackage(term);
if (package == null) {

View File

@@ -396,7 +396,6 @@ bool hasUnicode(String text) {
return unicodeSymbols.length > 0;
}
String removeTrailingZeros(double number) {
String result = number.toString();
result = result.indexOf('.') > 0

View File

@@ -13,6 +13,7 @@ import 'package:fcs/pages/widgets/defalut_delivery_address.dart';
import 'package:fcs/pages/widgets/display_text.dart';
import 'package:fcs/pages/widgets/fcs_id_icon.dart';
import 'package:fcs/pages/widgets/local_app_bar.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';
@@ -62,9 +63,11 @@ class _ProfileState extends State<Profile> {
@override
Widget build(BuildContext context) {
MainModel mainModel = Provider.of<MainModel>(context);
LanguageModel languageModel = Provider.of<LanguageModel>(context);
if (mainModel.user == null) {
return Container();
}
buildLanguage(languageModel);
DeliveryAddressModel deliveryAddressModel =
Provider.of<DeliveryAddressModel>(context);
@@ -172,6 +175,12 @@ class _ProfileState extends State<Profile> {
builder: (context) => DeliveryAddressList()));
},
),
buildLanguageWidget(
context: context,
text: "profile.language",
iconData: Icons.language,
isEng: languageModel.isEng,
),
getPrivilegeBox(context),
SizedBox(height: 15),
logoutbutton,
@@ -183,6 +192,69 @@ class _ProfileState extends State<Profile> {
);
}
Widget buildLanguageWidget(
{required String text,
required BuildContext context,
IconData? iconData,
required bool isEng}) {
return Row(
children: <Widget>[
Icon(iconData, color: primaryColor),
const SizedBox(width: 15),
Expanded(
child: LocalText(
context,
text,
fontSize: 15.0,
color: Colors.black54,
),
),
Row(
children: [
isEng
? Image.asset(
'icons/flags/png/us.png',
package: 'country_icons',
fit: BoxFit.fitWidth,
width: 25,
)
: Image.asset(
'icons/flags/png/mm.png',
package: 'country_icons',
fit: BoxFit.fitWidth,
width: 25,
),
Container(
width: 100,
padding: const EdgeInsets.only(left: 15),
child: DropdownButton(
value: selectedLanguage,
underline: const SizedBox(),
isExpanded: true,
items: languagesList
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
style: const TextStyle(fontSize: 14),
));
}).toList(),
onChanged: _selectedDropdown)),
],
)
],
);
}
_selectedDropdown(selected) {
var languageModel = Provider.of<LanguageModel>(context, listen: false);
languageModel.saveLanguage(selected);
setState(() {
selectedLanguage = selected;
});
}
Widget getPrivilegeBox(BuildContext context) {
User? user = Provider.of<MainModel>(context, listen: false).user;
List<Privilege> _privileges =

View File

@@ -0,0 +1,120 @@
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/widgets/local_app_bar.dart';
import 'package:fcs/pages/widgets/local_button.dart';
import 'package:fcs/pages/widgets/local_text.dart';
import 'package:fcs/pages/widgets/progress.dart';
import 'package:flutter/material.dart';
import 'package:pin_input_text_field/pin_input_text_field.dart';
class PinLoginPage extends StatefulWidget {
const PinLoginPage({super.key});
@override
State<PinLoginPage> createState() => _PinLoginPageState();
}
class _PinLoginPageState extends State<PinLoginPage> {
bool _isLoading = false;
late String pin;
TextEditingController _fcsIdCtl = new TextEditingController();
@override
void initState() {
pin = "";
super.initState();
}
Widget build(BuildContext context) {
final fcsIdBox = TextFormField(
controller: _fcsIdCtl,
autofocus: true,
style: TextStyle(
fontSize: 15, color: Colors.black87, fontWeight: FontWeight.w500),
cursorColor: primaryColor,
keyboardType: TextInputType.text,
decoration: new InputDecoration(
contentPadding: EdgeInsets.all(0),
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)),
),
);
final pinLoginLogo = Container(
width: 70,
height: 70,
child: FittedBox(
child: Image.asset(
"assets/logo.jpg",
),
fit: BoxFit.fitHeight,
),
);
final loginBtn = Padding(
padding: EdgeInsets.only(top: 30),
child: LocalButton(
textKey: "welcome.pinlogin",
callBack: () {},
),
);
return LocalProgress(
inAsyncCall: _isLoading,
child: new Scaffold(
appBar: LocalAppBar(
backgroundColor: null,
arrowColor: Colors.black,
),
body: ListView(
padding: EdgeInsets.only(top: 20, left: 15, right: 15, bottom: 20),
children: [
pinLoginLogo,
Padding(
padding: EdgeInsets.only(top: 20, bottom: 20),
child: Center(
child: LocalText(context, "welcome.pinlogin",
color: Colors.black, fontSize: 18),
),
),
LocalText(
context,
"welcome.pinlogin.fcsid",
color: Colors.black54,
fontSize: 15,
),
fcsIdBox,
Padding(
padding: EdgeInsets.only(top: 25, bottom: 20),
child: LocalText(
context,
"welcome.pinlogin.pin",
color: Colors.black54,
fontSize: 15,
)),
PinInputTextField(
cursor: Cursor(
color: primaryColor, enabled: true, width: 2, height: 23),
pinLength: 6,
decoration: BoxLooseDecoration(
strokeColorBuilder: PinListenColorBuilder(
primaryColor, Colors.grey.shade400)),
textInputAction: TextInputAction.done,
autoFocus: false,
onChanged: _pinChange,
),
loginBtn,
],
)),
);
}
_pinChange(pin) {
setState(() {
this.pin = pin;
});
}
}

View File

@@ -106,4 +106,29 @@ class StaffModel extends BaseModel {
}
return users;
}
Future<User?> getUser(String userID) async {
if (userID == "") return null;
String path = "/$user_collection";
try {
var snap = await FirebaseFirestore.instance
.collection(path)
.doc(userID)
.get(const GetOptions(source: Source.server));
if (snap.data() == null) return null;
Map<String, dynamic>? data = snap.data() as Map<String, dynamic>;
if (data['delete_time'] == 0) {
User user = User.fromMap(data, snap.id);
return user;
} else {
return null;
}
} catch (e) {
log.warning("Error!! $e");
}
return null;
}
}

View File

@@ -72,7 +72,7 @@ class _StaffEditorState extends State<StaffEditor> {
}),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Icon(p.iconData, size: 50, color: Colors.black38),
child: Icon(p.iconData, size: 30, color: Colors.black38),
),
Expanded(
child: Column(
@@ -80,10 +80,10 @@ class _StaffEditorState extends State<StaffEditor> {
children: <Widget>[
new Text(
p.name ?? "",
style: TextStyle(fontSize: 15.0, color: primaryColor),
style: TextStyle(fontSize: 15.0, color: Colors.black),
),
Text(p.desc ?? "",
style: TextStyle(fontSize: 13, color: Colors.grey[600]))
style: TextStyle(fontSize: 13, color: labelColor))
],
),
),
@@ -155,15 +155,22 @@ class _StaffEditorState extends State<StaffEditor> {
],
);
final updateButton = fcsButton(
final updateButton = Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: fcsButton(
context,
getLocalString(context, 'staff.update'),
callack: _save,
),
);
final addButton = fcsButton(
final addButton = Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: fcsButton(
context,
getLocalString(context, 'staff.add'),
callack: _add,
),
);
return LocalProgress(
@@ -196,9 +203,7 @@ class _StaffEditorState extends State<StaffEditor> {
Container(
child: isNew ? addButton : updateButton,
),
SizedBox(
height: 10,
)
SizedBox(height: 20)
],
),
),

View File

@@ -0,0 +1,203 @@
import 'package:fcs/domain/entities/user.dart';
import 'package:fcs/domain/vo/privilege.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/main/util.dart';
import 'package:fcs/pages/staff/model/staff_model.dart';
import 'package:fcs/pages/widgets/display_text.dart';
import 'package:fcs/pages/widgets/fcs_id_icon.dart';
import 'package:fcs/pages/widgets/local_app_bar.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:flutter_vector_icons/flutter_vector_icons.dart';
import 'package:provider/provider.dart';
import 'staff_pin_editor.dart';
import 'staff_privilege_editor.dart';
class StaffInfo extends StatefulWidget {
final User staff;
const StaffInfo({required this.staff});
@override
_StaffInfoState createState() => _StaffInfoState();
}
class _StaffInfoState extends State<StaffInfo> {
bool _isLoading = false;
late User _staff;
List<Privilege> _privileges = [];
@override
void initState() {
_staff = widget.staff;
_init();
super.initState();
}
_init() {
List<Privilege> list =
Provider.of<StaffModel>(context, listen: false).privileges;
list.forEach((p) => _staff.privileges.contains(p.id)
? p.isChecked = true
: p.isChecked = false);
_privileges = list.where((e) => e.isChecked ?? false).toList();
if (mounted) {
setState(() {});
}
}
List<Widget> showprivilegeList(BuildContext context) {
return _privileges.map((p) {
return new ListTile(
title: new Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Icon(p.iconData, size: 30, color: Colors.black38),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
p.name ?? "",
style: TextStyle(fontSize: 15.0, color: Colors.black),
),
Text(p.desc ?? "",
style: TextStyle(fontSize: 13, color: labelColor))
],
),
),
],
));
}).toList();
}
@override
Widget build(BuildContext context) {
final fcsIdBox = DisplayText(
text: _staff.fcsID,
labelTextKey: "customer.fcs.id",
icon: FcsIDIcon(),
);
final namebox = DisplayText(
text: _staff.name,
labelTextKey: "customer.name",
iconData: Icons.person,
);
final phoneNumberBox = Row(
children: <Widget>[
Expanded(
child: DisplayText(
text: _staff.phoneNumber,
labelTextKey: "customer.phone",
iconData: Icons.phone,
)),
IconButton(
icon: Icon(Icons.open_in_new, color: primaryColor),
onPressed: () => call(context, _staff.phoneNumber!)),
],
);
final pinBox = Row(
children: <Widget>[
Expanded(
child: DisplayText(
text: _staff.enablePinLogin ? "Enabled" : "Disabled",
labelTextKey: "staff.pin_login",
iconData: MaterialCommunityIcons.lock)),
SizedBox(
width: 65,
height: 35,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
padding: EdgeInsets.all(5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0)),
side: BorderSide(color: primaryColor),
),
onPressed: () async {
bool? updated = await Navigator.of(context).push(
CupertinoPageRoute(
builder: (context) => StaffPinEditor(staff: _staff)));
if (updated ?? false) {
User? _u =
await context.read<StaffModel>().getUser(widget.staff.id!);
if (_u == null) return;
_staff = _u;
_init();
}
},
child: LocalText(context, 'staff.edit_pin',
color: primaryColor, fontSize: 13),
),
)
],
);
final privilegeTitleBox = Row(
children: [
Expanded(
child: LocalText(context, 'staff.privilege.title',
color: Colors.black54, fontSize: 15)),
SizedBox(
width: 100,
height: 35,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
padding: EdgeInsets.all(5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0)),
side: BorderSide(color: primaryColor),
),
onPressed: () async {
bool? updated = await Navigator.of(context).push(
CupertinoPageRoute(
builder: (context) =>
StaffPrivilegeEditor(staff: _staff)));
if (updated ?? false) {
User? _u =
await context.read<StaffModel>().getUser(widget.staff.id!);
if (_u == null) return;
_staff = _u;
_init();
}
},
child: LocalText(context, 'staff.edit_privileges',
color: primaryColor, fontSize: 13),
),
)
],
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: LocalAppBar(
labelKey: "staff.form.title",
backgroundColor: Colors.white,
labelColor: primaryColor,
arrowColor: primaryColor),
body: Padding(
padding: const EdgeInsets.only(left: 12.0, right: 12),
child: ListView(
children: <Widget>[
fcsIdBox,
phoneNumberBox,
namebox,
pinBox,
const SizedBox(height: 10),
privilegeTitleBox,
Column(children: showprivilegeList(context)),
SizedBox(height: 30)
],
),
),
));
}
}

View File

@@ -12,6 +12,7 @@ import 'package:provider/provider.dart';
import '../../pagination/paginator_listview.dart';
import 'staff_editor.dart';
import 'staff_info.dart';
class StaffList extends StatefulWidget {
@override
@@ -57,23 +58,19 @@ class _StaffListState extends State<StaffList> {
return InkWell(
onTap: () {
Navigator.of(context).push(
CupertinoPageRoute(builder: (context) => StaffEditor(staff: user)));
CupertinoPageRoute(builder: (context) => StaffInfo(staff: user)));
},
child: Row(
children: <Widget>[
Expanded(
child: new Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: new Row(
children: <Widget>[
new Padding(
padding: new EdgeInsets.symmetric(
horizontal: 32.0 - dotSize / 2),
child: Icon(
MaterialCommunityIcons.account_hard_hat,
color: primaryColor,
size: 40,
),
padding: new EdgeInsets.symmetric(horizontal: 15),
child: Icon(MaterialCommunityIcons.account_hard_hat,
color: primaryColor, size: 30),
),
new Expanded(
child: new Column(
@@ -84,7 +81,15 @@ class _StaffListState extends State<StaffList> {
style: new TextStyle(fontSize: 15.0),
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
padding: const EdgeInsets.only(top: 2),
child: new Text(
user.fcsID ?? "",
style: new TextStyle(
fontSize: 15.0, color: Colors.grey),
),
),
Padding(
padding: const EdgeInsets.only(top: 2),
child: new Text(
user.phoneNumber ?? "",
style: new TextStyle(

View File

@@ -0,0 +1,192 @@
import 'package:fcs/domain/entities/user.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/widgets/local_app_bar.dart';
import 'package:fcs/pages/widgets/progress.dart';
import 'package:flutter/material.dart';
import 'package:pin_input_text_field/pin_input_text_field.dart';
import '../main/util.dart';
import '../widgets/local_text.dart';
class StaffPinEditor extends StatefulWidget {
final User staff;
const StaffPinEditor({required this.staff});
@override
_StaffPinEditorState createState() => _StaffPinEditorState();
}
class _StaffPinEditorState extends State<StaffPinEditor> {
bool _isLoading = false;
late User _staff;
bool _enablePinLogin = false;
String _newPin = "";
String _confirmPin = "";
@override
void initState() {
_staff = widget.staff;
_enablePinLogin = _staff.enablePinLogin;
_newPin = _staff.pinDigit ?? "";
_confirmPin = _staff.confirmPinDigit ?? "";
super.initState();
}
@override
Widget build(BuildContext context) {
final enablePinBox = Row(
children: [
Checkbox(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: const VisualDensity(
horizontal: VisualDensity.minimumDensity,
vertical: VisualDensity.minimumDensity),
activeColor: primaryColor,
side: BorderSide(color: Colors.black38, width: 2),
value: _enablePinLogin,
onChanged: (value) {
setState(() {
_enablePinLogin = value ?? false;
});
},
),
const SizedBox(width: 15),
LocalText(context, 'staff.enable_pin',
fontSize: 15, color: Colors.black)
],
);
final newPinBox = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocalText(context, 'staff.new_pin',
color: Colors.black54, fontSize: 15),
const SizedBox(height: 8),
PinInputTextField(
cursor: Cursor(
color: primaryColor, enabled: true, width: 2, height: 23),
pinLength: 6,
decoration: BoxLooseDecoration(
strokeColorBuilder:
PinListenColorBuilder(primaryColor, Colors.grey.shade400)),
textInputAction: TextInputAction.done,
autoFocus: true,
onChanged: _newPinChange),
],
);
final confirmPinBox = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocalText(context, 'staff.confirm_pin',
color: Colors.black54, fontSize: 15),
const SizedBox(height: 8),
PinInputTextField(
pinLength: 6,
cursor: Cursor(
color: primaryColor, enabled: true, width: 2, height: 23),
decoration: BoxLooseDecoration(
strokeColorBuilder:
PinListenColorBuilder(primaryColor, Colors.grey.shade400)),
textInputAction: TextInputAction.done,
autoFocus: false,
onChanged: _confirmPinChange),
],
);
final saveButton = Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: fcsButton(
context,
getLocalString(context, 'btn.save'),
callack: _save,
),
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: LocalAppBar(
labelKey: "staff.pin.title",
backgroundColor: Colors.white,
labelColor: primaryColor,
arrowColor: primaryColor),
body: Padding(
padding: const EdgeInsets.only(left: 15.0, right: 15),
child: ListView(
children: <Widget>[
Column(
children: [
Text(_staff.name ?? "",
style: TextStyle(color: Colors.black, fontSize: 18)),
Text(_staff.fcsID ?? "",
style: TextStyle(color: Colors.black, fontSize: 15)),
],
),
const SizedBox(height: 20),
enablePinBox,
const SizedBox(height: 30),
newPinBox,
SizedBox(height: 30),
confirmPinBox,
SizedBox(height: 30),
saveButton,
SizedBox(height: 30)
],
),
),
));
}
_newPinChange(pin) {
setState(() {
this._newPin = pin;
});
}
_confirmPinChange(pin) {
setState(() {
this._confirmPin = pin;
});
}
_save() async {
if (_newPin == "") {
showMsgDialog(context, "Error", "Invalid new PIN");
return;
}
if (_newPin.length < 6) {
showMsgDialog(context, "Error", "New PIN must be 6 digits");
return;
}
if (_confirmPin == "") {
showMsgDialog(context, "Error", "Invalid confirm PIN");
return;
}
if (_confirmPin.length < 6) {
showMsgDialog(context, "Error", "Confirm PIN must be 6 digits");
return;
}
if (_confirmPin != _newPin) {
showMsgDialog(context, "Error", "Those pins didnt match. Try again.");
return;
}
setState(() {
_isLoading = true;
});
try {
Navigator.pop(context, true);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

View File

@@ -0,0 +1,149 @@
import 'package:fcs/domain/entities/user.dart';
import 'package:fcs/domain/vo/privilege.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/staff/model/staff_model.dart';
import 'package:fcs/pages/widgets/local_app_bar.dart';
import 'package:fcs/pages/widgets/progress.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../main/util.dart';
import '../widgets/local_text.dart';
class StaffPrivilegeEditor extends StatefulWidget {
final User staff;
const StaffPrivilegeEditor({required this.staff});
@override
_StaffPrivilegeEditorState createState() => _StaffPrivilegeEditorState();
}
class _StaffPrivilegeEditorState extends State<StaffPrivilegeEditor> {
bool _isLoading = false;
late User _staff;
List<Privilege> _privileges = [];
@override
void initState() {
_staff = widget.staff;
_privileges = Provider.of<StaffModel>(context, listen: false).privileges;
_privileges.forEach((p) => _staff.privileges.contains(p.id)
? p.isChecked = true
: p.isChecked = false);
super.initState();
}
List<Widget> showprivilegeList(BuildContext context) {
return _privileges.map((p) {
return new ListTile(
title: InkWell(
onTap: () {
setState(() {
p.isChecked = p.isChecked == null ? true : !p.isChecked!;
});
},
child: new Row(
children: <Widget>[
new Checkbox(
value: p.isChecked == null ? false : p.isChecked,
activeColor: primaryColor,
side: BorderSide(color: Colors.black38, width: 2),
onChanged: (bool? value) {
if (value != null)
setState(() {
p.isChecked = value;
});
}),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Icon(p.iconData, size: 30, color: Colors.black38),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
p.name ?? "",
style: TextStyle(fontSize: 15.0, color: Colors.black),
),
Text(p.desc ?? "",
style: TextStyle(fontSize: 13, color: labelColor))
],
),
),
],
),
));
}).toList();
}
@override
Widget build(BuildContext context) {
final saveButton = Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: fcsButton(
context,
getLocalString(context, 'btn.save'),
callack: _save,
),
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: LocalAppBar(
labelKey: "staff.privilege.title",
backgroundColor: Colors.white,
labelColor: primaryColor,
arrowColor: primaryColor),
body: Padding(
padding: const EdgeInsets.only(left: 12.0, right: 12),
child: ListView(
children: <Widget>[
Column(
children: [
Text(_staff.name ?? "",
style: TextStyle(color: Colors.black, fontSize: 18)),
Text(_staff.fcsID ?? "",
style: TextStyle(color: Colors.black, fontSize: 15)),
],
),
const SizedBox(height: 10),
LocalText(context, 'staff.privilege.title',
color: Colors.black54, fontSize: 15),
Column(
children: showprivilegeList(context),
),
saveButton,
SizedBox(height: 20)
],
),
),
));
}
List<String> privilegesIDs() {
return this
._privileges
.where((p) => p.isChecked!)
.map((p) => p.id)
.toList();
}
_save() async {
setState(() {
_isLoading = true;
});
StaffModel staffModel = Provider.of<StaffModel>(context, listen: false);
try {
await staffModel.updatePrivileges(widget.staff.id!, privilegesIDs());
Navigator.pop(context, true);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}