From 6f43fd777a10c0fd674ad4511db159b7c8e8c3ac Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 20 Jan 2026 14:17:08 +0300 Subject: [PATCH 1/5] user shift information added. --- lib/controllers/api_routes/urls.dart | 5 +- .../providers/api/user_provider.dart | 17 + lib/extensions/string_extensions.dart | 5 + lib/models/user_rota_model.dart | 108 ++++ .../common_widgets/app_dashed_button.dart | 10 +- lib/views/pages/user/profile_page.dart | 540 +++++++++--------- 6 files changed, 406 insertions(+), 279 deletions(-) create mode 100644 lib/models/user_rota_model.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index eed578cf..067265f0 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -8,10 +8,10 @@ class URLs { // static const host1 = "https://atomsmuat.hmg.com"; // local UAT url // static final String _baseUrl = "$_host/mobile"; // host local UAT - // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis + static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis // static final String _baseUrl = "$_host/mobile"; // host local UAT and for internal audit dev // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM - static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module + // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation static const String chatHubUrl = "https://apiderichat.hmg.com/chathub"; @@ -48,6 +48,7 @@ class URLs { static get sendForgetPasswordOtp => "$_baseUrl/Account/SendForgotPasswordOtp"; // send OTP. static get sendForgetPasswordValidateOtp => "$_baseUrl/Account/SendForgotPasswordValidateOtp"; // validate OTP. static get updateNewPassword => "$_baseUrl/Account/UpdatenewPassword"; // validate OTP. + static get getUserRota => "$_baseUrl/Account/GetUserRota"; // validate OTP. // static get login => "$_baseUrl/MobileAuth/LoginIntegration"; // mobile login static get register => "$_baseUrl/handle/create/user"; // post diff --git a/lib/controllers/providers/api/user_provider.dart b/lib/controllers/providers/api/user_provider.dart index d82a6b2d..64cec305 100644 --- a/lib/controllers/providers/api/user_provider.dart +++ b/lib/controllers/providers/api/user_provider.dart @@ -15,6 +15,7 @@ import 'package:test_sa/models/new_models/update_password.dart'; import 'package:test_sa/models/new_models/verify_otp_model.dart'; import 'package:test_sa/models/site_contact_info_model.dart'; import 'package:test_sa/models/user.dart'; +import 'package:test_sa/models/user_rota_model.dart'; import 'package:test_sa/new_views/swipe_module/models/swipe_model.dart'; import 'package:test_sa/new_views/swipe_module/models/swipe_transaction_history.dart'; import 'package:test_sa/new_views/swipe_module/models/swipe_transaction_model.dart'; @@ -46,7 +47,9 @@ class UserProvider extends ChangeNotifier { bool get isNurse => user!.type == UsersTypes.normal_user; bool get isAssessor => user!.type == UsersTypes.assessor || user!.type == UsersTypes.assessorTl; + bool get isQualityUser => user!.type == UsersTypes.qualityUser; + bool get isCommissioningEngineer => user!.type == UsersTypes.commissioningEngineer; VerifyOtpModel _verifyOtpModel = VerifyOtpModel(); @@ -443,6 +446,20 @@ class UserProvider extends ChangeNotifier { } } + Future> getUserRota() async { + Response response; + try { + response = await ApiManager.instance.get(URLs.getUserRota); + if (response.statusCode >= 200 && response.statusCode < 300) { + List dataList = json.decode(response.body); + return List.generate(dataList.length, (index) => UserRotaModel.fromJson(dataList[index])); + } + return []; + } catch (error) { + return []; + } + } + Future getSwipeTransactionHistory({required String userId, required DateTime dateFrom, required DateTime dateTo}) async { isLoading = true; notifyListeners(); diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 855560d1..74f9d0bf 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -6,6 +6,11 @@ extension StringExtensions on String { void get showToast => Fluttertoast.showToast(msg: this); + String get toTime { + DateTime dateTime = DateFormat("HH:mm:ss").parse(this); + return DateFormat('hh:mm a').format(dateTime); + } + String get chatMsgTime { DateTime dateTime = DateTime.parse(this).toLocal(); return DateFormat('hh:mm a').format(dateTime); diff --git a/lib/models/user_rota_model.dart b/lib/models/user_rota_model.dart new file mode 100644 index 00000000..f360e994 --- /dev/null +++ b/lib/models/user_rota_model.dart @@ -0,0 +1,108 @@ +class UserRotaModel { + String? employeeNumber; + String? scheduleDate; + String? shiftName; + String? breakTime; + String? shiftActualStartDatetime; + String? shTActualStartTime; + String? shiftActualEndDateTime; + String? shTActualEndTime; + String? approvedStartDatetime; + String? approvedStartTime; + String? approvedStartReasonDesc; + String? approvedEndDatetime; + String? approvedEndTime; + String? approvedEndReasonDesc; + String? remarks; + String? leaveTypeCode; + String? leaveDescription; + int? assetGroupId; + String? assetGroupName; + int? id; + String? createdBy; + String? createdDate; + String? modifiedBy; + String? modifiedDate; + + UserRotaModel( + {this.employeeNumber, + this.scheduleDate, + this.shiftName, + this.breakTime, + this.shiftActualStartDatetime, + this.shTActualStartTime, + this.shiftActualEndDateTime, + this.shTActualEndTime, + this.approvedStartDatetime, + this.approvedStartTime, + this.approvedStartReasonDesc, + this.approvedEndDatetime, + this.approvedEndTime, + this.approvedEndReasonDesc, + this.remarks, + this.leaveTypeCode, + this.leaveDescription, + this.assetGroupId, + this.assetGroupName, + this.id, + this.createdBy, + this.createdDate, + this.modifiedBy, + this.modifiedDate}); + + UserRotaModel.fromJson(Map json) { + employeeNumber = json['employeeNumber']; + scheduleDate = json['scheduleDate']; + shiftName = json['shiftName']; + breakTime = json['break']; + shiftActualStartDatetime = json['shiftActualStartDatetime']; + shTActualStartTime = json['shTActualStartTime']; + shiftActualEndDateTime = json['shiftActualEndDateTime']; + shTActualEndTime = json['shTActualEndTime']; + approvedStartDatetime = json['approvedStartDatetime']; + approvedStartTime = json['approvedStartTime']; + approvedStartReasonDesc = json['approvedStartReasonDesc']; + approvedEndDatetime = json['approvedEndDatetime']; + approvedEndTime = json['approvedEndTime']; + approvedEndReasonDesc = json['approvedEndReasonDesc']; + remarks = json['remarks']; + leaveTypeCode = json['leaveTypeCode']; + leaveDescription = json['leaveDescription']; + assetGroupId = json['assetGroupId']; + assetGroupName = json['assetGroupName']; + id = json['id']; + createdBy = json['createdBy']; + createdDate = json['createdDate']; + modifiedBy = json['modifiedBy']; + modifiedDate = json['modifiedDate']; + } + + Map toJson() { + final Map data = new Map(); + data['employeeNumber'] = this.employeeNumber; + data['scheduleDate'] = this.scheduleDate; + data['shiftName'] = this.shiftName; + data['break'] = this.breakTime; + data['shiftActualStartDatetime'] = this.shiftActualStartDatetime; + data['shTActualStartTime'] = this.shTActualStartTime; + data['shiftActualEndDateTime'] = this.shiftActualEndDateTime; + data['shTActualEndTime'] = this.shTActualEndTime; + data['approvedStartDatetime'] = this.approvedStartDatetime; + data['approvedStartTime'] = this.approvedStartTime; + data['approvedStartReasonDesc'] = this.approvedStartReasonDesc; + data['approvedEndDatetime'] = this.approvedEndDatetime; + data['approvedEndTime'] = this.approvedEndTime; + data['approvedEndReasonDesc'] = this.approvedEndReasonDesc; + data['remarks'] = this.remarks; + data['leaveTypeCode'] = this.leaveTypeCode; + data['leaveDescription'] = this.leaveDescription; + data['assetGroupId'] = this.assetGroupId; + data['assetGroupName'] = this.assetGroupName; + data['id'] = this.id; + data['createdBy'] = this.createdBy; + data['createdDate'] = this.createdDate; + data['modifiedBy'] = this.modifiedBy; + data['modifiedDate'] = this.modifiedDate; + return data; + } +} diff --git a/lib/new_views/common_widgets/app_dashed_button.dart b/lib/new_views/common_widgets/app_dashed_button.dart index 9e805de8..2e733ef9 100644 --- a/lib/new_views/common_widgets/app_dashed_button.dart +++ b/lib/new_views/common_widgets/app_dashed_button.dart @@ -10,22 +10,24 @@ import '../app_style/app_color.dart'; class AppDashedButton extends StatelessWidget { final String title; final VoidCallback onPressed; + double? width; double? height; Color? buttonColor; Widget? icon; + EdgeInsets? padding; - AppDashedButton({required this.title, required this.onPressed, Key? key, this.height, this.icon, this.buttonColor}) : super(key: key); + AppDashedButton({required this.title, required this.onPressed, Key? key, this.height, this.icon, this.buttonColor, this.padding, this.width}) : super(key: key); @override Widget build(BuildContext context) { return Container( - width: double.infinity, + width: width ?? double.infinity, height: height, - padding: EdgeInsets.symmetric(horizontal: 2.toScreenWidth), + padding: padding ?? EdgeInsets.symmetric(horizontal: 2.toScreenWidth), decoration: BoxDecoration(color: AppColor.background(context), borderRadius: BorderRadius.circular(10)), child: DottedBorder( strokeWidth: 1, - padding: EdgeInsets.symmetric(vertical: 16.toScreenHeight, horizontal: 16.toScreenWidth), + padding: padding ?? EdgeInsets.symmetric(vertical: 16.toScreenHeight, horizontal: 16.toScreenWidth), color: context.isDark ? AppColor.primary40 : buttonColor ?? AppColor.black20, dashPattern: const [4, 3], radius: const Radius.circular(10), diff --git a/lib/views/pages/user/profile_page.dart b/lib/views/pages/user/profile_page.dart index 9af6bb61..213df9b7 100644 --- a/lib/views/pages/user/profile_page.dart +++ b/lib/views/pages/user/profile_page.dart @@ -9,10 +9,15 @@ import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/user.dart'; +import 'package:test_sa/models/user_rota_model.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/new_views/common_widgets/app_dashed_button.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; + import 'package:test_sa/views/pages/user/update_user_contact_info_bottomsheet.dart'; import '../../../new_views/app_style/app_color.dart'; @@ -51,129 +56,272 @@ class _ProfilePageState extends State { return Scaffold( key: _scaffoldKey, appBar: DefaultAppBar(title: context.translation.myProfile), - body: SafeArea( - child: ListView( - padding: const EdgeInsets.all(16), - children: [ - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: double.infinity, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Consumer(builder: (context, snapshot, _) { - return Stack( - alignment: Alignment.bottomRight, - children: [ - CircleAvatar( - radius: 45, - backgroundColor: const Color(0xFFE4E5E6), - child: Padding( - padding: const EdgeInsets.all(1), // Border radius - child: ClipOval( - child: snapshot.profileImage != null - ? Image.file(snapshot.profileImage!) - : (snapshot.user!.profilePhotoName?.isNotEmpty ?? false) - ? Image.network(snapshot.user!.profilePhotoName!) - : const Icon( - Icons.person, - size: 50, - color: Colors.white, - ), + body: Column( + children: [ + ListView( + padding: const EdgeInsets.all(16), + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Consumer(builder: (context, snapshot, _) { + return Stack( + alignment: Alignment.bottomRight, + children: [ + CircleAvatar( + radius: 45, + backgroundColor: const Color(0xFFE4E5E6), + child: Padding( + padding: const EdgeInsets.all(1), // Border radius + child: ClipOval( + child: snapshot.profileImage != null + ? Image.file(snapshot.profileImage!) + : (snapshot.user!.profilePhotoName?.isNotEmpty ?? false) + ? Image.network(snapshot.user!.profilePhotoName!) + : const Icon( + Icons.person, + size: 50, + color: Colors.white, + ), + ), ), ), - ), - CircleAvatar( - radius: 14, - backgroundColor: AppColor.primary70, - child: Padding( - padding: const EdgeInsets.all(1), // Border radius - child: snapshot.isLoading - ? const SizedBox(height: 16, width: 16, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) - : const Icon(Icons.upload, size: 16, color: Colors.white), - ), - ).onPress(snapshot.isLoading - ? null - : () async { - final pickedFile = await ImagePicker().pickImage(source: ImageSource.gallery, imageQuality: 70, maxWidth: 800, maxHeight: 800); + // CircleAvatar( + // radius: 14, + // backgroundColor: AppColor.primary70, + // child: Padding( + // padding: const EdgeInsets.all(1), // Border radius + // child: snapshot.isLoading + // ? const SizedBox(height: 16, width: 16, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) + // : const Icon(Icons.upload, size: 16, color: Colors.white), + // ), + // ).onPress(snapshot.isLoading + // ? null + // : () async { + // final pickedFile = await ImagePicker().pickImage(source: ImageSource.gallery, imageQuality: 70, maxWidth: 800, maxHeight: 800); + // + // if (pickedFile != null) { + // CroppedFile? croppedFile = await ImageCropper().cropImage( + // sourcePath: pickedFile.path, + // aspectRatio: CropAspectRatio(ratioX: 1, ratioY: 1), + // uiSettings: [ + // AndroidUiSettings( + // toolbarTitle: 'ATOMS', + // toolbarColor: Colors.white, + // toolbarWidgetColor: color, + // initAspectRatio: CropAspectRatioPreset.square, + // lockAspectRatio: false, + // ), + // IOSUiSettings(title: 'ATOMS'), + // ], + // ); + // if (croppedFile != null) { + // await snapshot.uploadProfileImage(_user.userID!, File(croppedFile.path)); + // Provider.of(context, listen: false).setUser(_userProvider.user!); + // } + // } + // }), + ], + ); + }), + 8.height, + (_user.username ?? "").heading3(context).custom(fontWeight: FontWeight.w600, color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + ], + ), + ), + 16.height, + Text( + '${context.translation.email}: ${_user.email ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + '${context.translation.phoneNumber}: ${_user.phoneNumber ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + '${context.translation.extensionNo}: ${_user.extensionNo ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + // if ((_user.phoneNumber ?? "").isNotEmpty) ...[ + // const Divider().defaultStyle(context), + // 8.height, + // (_user.phoneNumber??"").heading5(context), + // 8.height, + // ], + // if ((_user.extensionNo ?? "").isNotEmpty) ...[ + // const Divider().defaultStyle(context), + // 8.height, + // _user.extensionNo!.heading5(context), + // 8.height, + // ], + if ((_user.departmentName?.length ?? 0) > 0) const Divider().defaultStyle(context), + ListView.builder( + physics: const NeverScrollableScrollPhysics(), + itemCount: _user.departmentName?.length ?? 0, + shrinkWrap: true, + itemBuilder: (context, index) { + return (_user.departmentName![index] ?? "N/A").heading5(context).paddingOnly(top: 8, bottom: 8); + }, + ), + if ((_user.clientName ?? "").isNotEmpty) ...[ + const Divider().defaultStyle(context), + 8.height, + _user.clientName!.heading5(context).custom(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + 8.height, + ], + + Selector( + selector: (_, myModel) => myModel.isLoading, // Selects only the userName + builder: (_, isLoading, __) { + return AppDashedButton( + title: isLoading ? "Uploading..." : "Update Image", + height: 50, + width: MediaQuery.sizeOf(context).width / 2, + padding: EdgeInsets.all(4), + icon: isLoading + ? const SizedBox(height: 16, width: 16, child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2)) + : 'attachment_icon'.toSvgAsset(height: 24, width: 24), + buttonColor: AppColor.primary10, + onPressed: () async { + if (isLoading) return; - if (pickedFile != null) { - CroppedFile? croppedFile = await ImageCropper().cropImage( - sourcePath: pickedFile.path, - aspectRatio: CropAspectRatio(ratioX: 1, ratioY: 1), - uiSettings: [ - AndroidUiSettings( - toolbarTitle: 'ATOMS', - toolbarColor: Colors.white, - toolbarWidgetColor: color, - initAspectRatio: CropAspectRatioPreset.square, - lockAspectRatio: false, + final pickedFile = await ImagePicker().pickImage(source: ImageSource.gallery, imageQuality: 70, maxWidth: 800, maxHeight: 800); + + if (pickedFile != null) { + CroppedFile? croppedFile = await ImageCropper().cropImage( + sourcePath: pickedFile.path, + aspectRatio: CropAspectRatio(ratioX: 1, ratioY: 1), + uiSettings: [ + AndroidUiSettings( + toolbarTitle: 'ATOMS', + toolbarColor: Colors.white, + toolbarWidgetColor: color, + initAspectRatio: CropAspectRatioPreset.square, + lockAspectRatio: false, + ), + IOSUiSettings(title: 'ATOMS'), + ], + ); + if (croppedFile != null) { + await context.userProvider.uploadProfileImage(_user.userID!, File(croppedFile.path)); + Provider.of(context, listen: false).setUser(_userProvider.user!); + } + } + }, + ).center; + }) + ], + ).toShadowContainer(context), + if (context.settingProvider.isUserFlowMedical) + FutureBuilder>( + future: _userProvider.getUserRota(), + builder: (cxt, snapshot) { + bool isLoading = snapshot.connectionState == ConnectionState.waiting; + + if ((!isLoading && (snapshot.hasError || (snapshot.data?.isEmpty ?? true)))) { + return const SizedBox(); + } + + return Container( + margin: const EdgeInsets.only(top: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Rota Details", + style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), + ), + 8.height, + if (!isLoading) + ListView.separated( + padding: EdgeInsets.zero, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (cxt, index) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // Text( + // 'Site: ${_user.clientName ?? "-"}', + // style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + // ), + Text( + 'Shift Name: ${snapshot.data![index].shiftName ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), - IOSUiSettings(title: 'ATOMS'), + Text( + 'Break: ${snapshot.data![index].breakTime ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + if (snapshot.data![index].leaveTypeCode?.isNotEmpty ?? false) + SizedBox( + width: double.infinity, + child: Column(children: [ + 8.height, + Text( + snapshot.data![index].leaveTypeCode ?? "", + style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, fontWeight: FontWeight.w600), + ), + Text( + snapshot.data![index].leaveDescription ?? "", + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + ]), + ) + else ...[ + Text( + 'Shift Start Time: ${snapshot.data![index].shTActualStartTime?.toTime ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + 'Shift Start Time: ${snapshot.data![index].shTActualEndTime?.toTime ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + ], ], - ); - if (croppedFile != null) { - await snapshot.uploadProfileImage(_user.userID!, File(croppedFile.path)); - Provider.of(context, listen: false).setUser(_userProvider.user!); - } - } - }), + ), + separatorBuilder: (cxt, index) => const Divider().defaultStyle(context), + itemCount: snapshot.data!.length), + if (isLoading) ...[ + Text( + 'Shift Start Time Time', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ).toShimmer(context: context, isShow: isLoading), + 4.height, + Text( + 'Shift Actual Actual Start Time', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ).toShimmer(context: context, isShow: isLoading), + ] ], - ); - }), - 8.height, - (_user.username ?? "").heading3(context).custom(fontWeight: FontWeight.w600, color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), - ], - ), - ), - 16.height, - Text( - '${context.translation.email}: ${_user.email ?? "-"}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - '${context.translation.phoneNumber}: ${_user.phoneNumber ?? "-"}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - '${context.translation.extensionNo}: ${_user.extensionNo ?? "-"}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), + ).toShadowContainer(context), + ); - // if ((_user.phoneNumber ?? "").isNotEmpty) ...[ - // const Divider().defaultStyle(context), - // 8.height, - // (_user.phoneNumber??"").heading5(context), - // 8.height, - // ], - // if ((_user.extensionNo ?? "").isNotEmpty) ...[ - // const Divider().defaultStyle(context), - // 8.height, - // _user.extensionNo!.heading5(context), - // 8.height, - // ], - if ((_user.departmentName?.length ?? 0) > 0) const Divider().defaultStyle(context), - ListView.builder( - physics: const NeverScrollableScrollPhysics(), - itemCount: _user.departmentName?.length ?? 0, - shrinkWrap: true, - itemBuilder: (context, index) { - return (_user.departmentName![index] ?? "N/A").heading5(context).paddingOnly(top: 8, bottom: 8); - }, - ), - if ((_user.clientName ?? "").isNotEmpty) ...[ - const Divider().defaultStyle(context), - 8.height, - _user.clientName!.heading5(context).custom(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), - 8.height, - ], - ], - ).toShadowContainer(context), - 16.height, - AppFilledButton( + // if (!isDeviceSupported) { + // return Text("Your device did not support Fingerprint/Face ID.", style: AppTextStyles.tinyFont.copyWith(fontSize: 11, color: Colors.red)); + // } + + // return Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // "Fingerprint/Face ID".heading5(context), + // customSwitch(localAuth).onPress(() { + // checkForLocalAuth(localAuth); + // }), + // ], + // ); + }), + ], + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( label: "Update Information", buttonColor: context.isDark ? AppColor.primary10 : AppColor.neutral50, onPressed: () { @@ -193,163 +341,9 @@ class _ProfilePageState extends State { ), title: "Update Information"); }, - ) - ], - ), - // child: Stack( - // children: [ - // Form( - // key: _formKey, - // child: ListView( - // children: [ - // //AppNameBar(), - // Hero( - // tag: "logo", - // child: Image( - // height: _height / 4, - // image: AssetImage("assets/images/logo.png"), - // ), - // ), - // Container( - // padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16), - // margin: EdgeInsets.symmetric(horizontal: 16), - // decoration: BoxDecoration(color: AColors.primaryColor, borderRadius: BorderRadius.circular(AppStyle.getBorderRadius(context)), boxShadow: [ - // BoxShadow( - // color: AColors.grey, - // offset: Offset(0, -1), - // ) - // ]), - // child: Column( - // children: [ - // ATextFormField( - // initialValue: _user.userName, - // hintText: context.translation.name, - // enable: false, - // prefixIconData: Icons.account_circle, - // style: Theme.of(context).textTheme.headline6, - // validator: (value) => Validator.hasValue(value) ? null : context.translation.nameValidateMessage, - // onSaved: (value) { - // _user.userName = value; - // }, - // ), - // SizedBox( - // height: 8, - // ), - // ATextFormField( - // initialValue: _user.email, - // hintText: context.translation.email, - // enable: false, - // prefixIconData: Icons.email, - // textInputType: TextInputType.emailAddress, - // style: Theme.of(context).textTheme.headline6, - // validator: (value) => Validator.isEmail(value) ? null : context.translation.emailValidateMessage, - // onSaved: (value) { - // _user.email = value; - // }, - // ), - // SizedBox( - // height: 8, - // ), - // AbsorbPointer( - // child: HospitalButton( - // hospital: Hospital(name: _user.clientName, id: _user.clientId), - // onHospitalPick: (hospital) { - // _user.clientName = hospital.name; - // _user.clientId = hospital.id; - // setState(() {}); - // }, - // ), - // ), - // SizedBox( - // height: 8, - // ), - // // DepartmentButton( - // // department: Department(name: _user.departmentName, id: _user.departmentId), - // // onDepartmentPick: (department) { - // // _user.departmentName = department.name; - // // _user.departmentId = department.id; - // // setState(() {}); - // // }, - // // ), - // SizedBox( - // height: 8, - // ), - // ATextFormField( - // initialValue: _user.phoneNumber, - // hintText: context.translation.phoneNumber, - // style: Theme.of(context).textTheme.headline6, - // prefixIconData: Icons.phone_android, - // validator: (value) => Validator.isPhoneNumber(value) ? null : context.translation.phoneNumberValidateMessage, - // textInputType: TextInputType.phone, - // onSaved: (value) { - // _user.phoneNumber = value; - // }, - // ), - // SizedBox( - // height: 8, - // ), - // // ATextFormField( - // // initialValue: _user.whatsApp, - // // hintText: context.translation.whatsApp, - // // style: Theme.of(context).textTheme.headline6, - // // prefixIconData: FontAwesomeIcons.whatsapp, - // // prefixIconSize: 36, - // // validator: (value) => - // // Validator.isPhoneNumber(value) ? null : context.translation.phoneNumberValidateMessage, - // // textInputType: TextInputType.phone, - // // onSaved: (value){ - // // _user.whatsApp = value; - // // }, - // // ), - // ], - // ), - // ), - // SizedBox( - // height: 16, - // ), - // Center( - // child: SizedBox( - // height: _width / 8, - // width: _width / 1.2, - // child: AButton( - // text: context.translation.update, - // onPressed: () async { - // // if (!_formKey.currentState.validate()) return; - // // _formKey.currentState.save(); - // // if (_user.departmentId == null) { - // // ScaffoldMessenger.of(context).showSnackBar(SnackBar( - // // content: Text(context.translation.unitRequired), - // // )); - // // return; - // // } - // // int status = await _userProvider.updateProfile( - // // user: _user, - // // host: _settingProvider.host, - // // ); - // // if (status >= 200 && status < 300) { - // // _settingProvider.setUser(_userProvider.user); - // // ScaffoldMessenger.of(context).showSnackBar(SnackBar( - // // content: Text(context.translation.requestCompleteSuccessfully), - // // )); - // // } else { - // // String errorMessage = HttpStatusManger.getStatusMessage(status: status, subtitle: context.translation); - // // ScaffoldMessenger.of(context).showSnackBar(SnackBar( - // // content: Text(errorMessage), - // // )); - // // } - // }, - // ), - // ), - // ), - // SizedBox( - // height: 32, - // ), - // ], - // ), - // ), - // ABackButton(), - // ], - // ), + ), + ) + ], ), ); } From c377c67f40492aee7f07381a25bc92c3582b676e Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 20 Jan 2026 17:02:02 +0300 Subject: [PATCH 2/5] rota module added. --- .../providers/api/user_provider.dart | 8 +- lib/views/pages/user/profile_page.dart | 111 +++++++++--------- 2 files changed, 60 insertions(+), 59 deletions(-) diff --git a/lib/controllers/providers/api/user_provider.dart b/lib/controllers/providers/api/user_provider.dart index 64cec305..a989cbde 100644 --- a/lib/controllers/providers/api/user_provider.dart +++ b/lib/controllers/providers/api/user_provider.dart @@ -446,17 +446,17 @@ class UserProvider extends ChangeNotifier { } } - Future> getUserRota() async { + Future getUserRota() async { Response response; try { response = await ApiManager.instance.get(URLs.getUserRota); if (response.statusCode >= 200 && response.statusCode < 300) { List dataList = json.decode(response.body); - return List.generate(dataList.length, (index) => UserRotaModel.fromJson(dataList[index])); + return UserRotaModel.fromJson(dataList.first); } - return []; + return null; } catch (error) { - return []; + return null ; } } diff --git a/lib/views/pages/user/profile_page.dart b/lib/views/pages/user/profile_page.dart index 213df9b7..e228923e 100644 --- a/lib/views/pages/user/profile_page.dart +++ b/lib/views/pages/user/profile_page.dart @@ -219,12 +219,11 @@ class _ProfilePageState extends State { ], ).toShadowContainer(context), if (context.settingProvider.isUserFlowMedical) - FutureBuilder>( + FutureBuilder( future: _userProvider.getUserRota(), builder: (cxt, snapshot) { bool isLoading = snapshot.connectionState == ConnectionState.waiting; - - if ((!isLoading && (snapshot.hasError || (snapshot.data?.isEmpty ?? true)))) { + if ((!isLoading && (snapshot.hasError || !snapshot.hasData))) { return const SizedBox(); } @@ -238,56 +237,6 @@ class _ProfilePageState extends State { style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), ), 8.height, - if (!isLoading) - ListView.separated( - padding: EdgeInsets.zero, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (cxt, index) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - // Text( - // 'Site: ${_user.clientName ?? "-"}', - // style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - // ), - Text( - 'Shift Name: ${snapshot.data![index].shiftName ?? "-"}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - 'Break: ${snapshot.data![index].breakTime ?? "-"}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - if (snapshot.data![index].leaveTypeCode?.isNotEmpty ?? false) - SizedBox( - width: double.infinity, - child: Column(children: [ - 8.height, - Text( - snapshot.data![index].leaveTypeCode ?? "", - style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, fontWeight: FontWeight.w600), - ), - Text( - snapshot.data![index].leaveDescription ?? "", - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - ]), - ) - else ...[ - Text( - 'Shift Start Time: ${snapshot.data![index].shTActualStartTime?.toTime ?? "-"}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - 'Shift Start Time: ${snapshot.data![index].shTActualEndTime?.toTime ?? "-"}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - ], - ], - ), - separatorBuilder: (cxt, index) => const Divider().defaultStyle(context), - itemCount: snapshot.data!.length), if (isLoading) ...[ Text( 'Shift Start Time Time', @@ -297,8 +246,60 @@ class _ProfilePageState extends State { Text( 'Shift Actual Actual Start Time', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ).toShimmer(context: context, isShow: isLoading), - ] + ).toShimmer(context: context, isShow: isLoading) + ] else + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // Text( + // 'Site: ${_user.clientName ?? "-"}', + // style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + // ), + Text( + 'Shift Name: ${snapshot.data!.shiftName ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + 'Break: ${snapshot.data!.breakTime ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + if (snapshot.data!.leaveTypeCode?.isNotEmpty ?? false) + SizedBox( + width: double.infinity, + child: Column(children: [ + 8.height, + Text( + snapshot.data!.leaveTypeCode ?? "", + style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, fontWeight: FontWeight.w600), + ), + Text( + snapshot.data!.leaveDescription ?? "", + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + ]), + ) + else ...[ + Text( + 'Shift Start Time: ${snapshot.data!.shiftActualStartDatetime?.chatMsgTime ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + 'Shift Start Time: ${snapshot.data!.shiftActualEndDateTime?.chatMsgTime ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + const Divider().defaultStyle(context), + Text( + 'Swipe In: ${snapshot.data!.shTActualStartTime?.toTime ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + 'Swipe Out: ${snapshot.data!.shTActualEndTime?.toTime ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + ], + ], + ), ], ).toShadowContainer(context), ); From 786d89404ee7d2426e7df0dd863f7bcc827b1b27 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 21 Jan 2026 17:14:55 +0300 Subject: [PATCH 3/5] shift start time and end time improvement. --- lib/views/pages/user/profile_page.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/pages/user/profile_page.dart b/lib/views/pages/user/profile_page.dart index e228923e..cab6b76d 100644 --- a/lib/views/pages/user/profile_page.dart +++ b/lib/views/pages/user/profile_page.dart @@ -281,11 +281,11 @@ class _ProfilePageState extends State { ) else ...[ Text( - 'Shift Start Time: ${snapshot.data!.shiftActualStartDatetime?.chatMsgTime ?? "-"}', + 'Shift Start Time: ${snapshot.data!.shiftName?.split('-').first.trim() ?? "-"}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), Text( - 'Shift Start Time: ${snapshot.data!.shiftActualEndDateTime?.chatMsgTime ?? "-"}', + 'Shift End Time: ${snapshot.data!.shiftName?.split('-').last.trim() ?? "-"}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), const Divider().defaultStyle(context), From 594fabd51a3897b35826a1f9532b141a07fa755c Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 29 Jan 2026 11:38:22 +0300 Subject: [PATCH 4/5] uat apk provided --- lib/controllers/api_routes/urls.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 067265f0..6fe56728 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -4,8 +4,8 @@ class URLs { static const String appReleaseBuildNumber = "32"; // static const host1 = "https://atomsm.hmg.com"; // production url - static const host1 = "https://atomsmdev.hmg.com"; // local DEV url - // static const host1 = "https://atomsmuat.hmg.com"; // local UAT url + // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url + static const host1 = "https://atomsmuat.hmg.com"; // local UAT url // static final String _baseUrl = "$_host/mobile"; // host local UAT static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis From d993a22b397dba667aef257e137e0fe69b316daf Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 2 Feb 2026 16:53:47 +0300 Subject: [PATCH 5/5] monthly calender added for rota --- .../providers/api/user_provider.dart | 20 +- lib/extensions/context_extension.dart | 2 + lib/extensions/widget_extensions.dart | 2 +- lib/views/pages/user/profile_page.dart | 358 ++++++++++++------ 4 files changed, 271 insertions(+), 111 deletions(-) diff --git a/lib/controllers/providers/api/user_provider.dart b/lib/controllers/providers/api/user_provider.dart index a989cbde..ec6f3242 100644 --- a/lib/controllers/providers/api/user_provider.dart +++ b/lib/controllers/providers/api/user_provider.dart @@ -446,17 +446,31 @@ class UserProvider extends ChangeNotifier { } } - Future getUserRota() async { + Future getUserTodayRota(String startDate, String endDate) async { Response response; try { - response = await ApiManager.instance.get(URLs.getUserRota); + response = await ApiManager.instance.get(URLs.getUserRota + "?fromDate=$startDate&toDate=$endDate"); if (response.statusCode >= 200 && response.statusCode < 300) { List dataList = json.decode(response.body); return UserRotaModel.fromJson(dataList.first); } return null; } catch (error) { - return null ; + return null; + } + } + + Future> getUserMonthlyRota(String startDate, String endDate) async { + Response response; + try { + response = await ApiManager.instance.get(URLs.getUserRota + "?fromDate=$startDate&toDate=$endDate"); + if (response.statusCode >= 200 && response.statusCode < 300) { + List dataList = json.decode(response.body); + return dataList.map((e) => UserRotaModel.fromJson(e)).toList(); + } + return []; + } catch (error) { + return []; } } diff --git a/lib/extensions/context_extension.dart b/lib/extensions/context_extension.dart index 96e79211..b7bb652a 100644 --- a/lib/extensions/context_extension.dart +++ b/lib/extensions/context_extension.dart @@ -14,6 +14,8 @@ extension BuildContextExtension on BuildContext { bool get isDark => Provider.of(this).theme == "dark"; + bool get isDarkNotListen => Provider.of(this, listen: false).theme == "dark"; + bool get isAr => Provider.of(this).language == "ar"; SettingProvider get settingProvider => Provider.of(this, listen: false); diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index 25aee136..946d2b29 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -186,5 +186,5 @@ extension WidgetExtensions on Widget { } extension DividerExtension on Divider { - Divider defaultStyle(BuildContext context) => Divider(thickness: 1, color: context.isDark ? AppColor.neutral20 : AppColor.neutral30); + Divider defaultStyle(BuildContext context) => Divider(thickness: 1, color: context.isDarkNotListen ? AppColor.neutral20 : AppColor.neutral30); } diff --git a/lib/views/pages/user/profile_page.dart b/lib/views/pages/user/profile_page.dart index cab6b76d..f1a0650a 100644 --- a/lib/views/pages/user/profile_page.dart +++ b/lib/views/pages/user/profile_page.dart @@ -3,7 +3,9 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; +import 'package:table_calendar/table_calendar.dart'; import 'package:test_sa/controllers/providers/api/user_provider.dart'; import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; @@ -17,9 +19,7 @@ import 'package:test_sa/modules/cm_module/views/components/action_button/footer_ import 'package:test_sa/new_views/common_widgets/app_dashed_button.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; - import 'package:test_sa/views/pages/user/update_user_contact_info_bottomsheet.dart'; - import '../../../new_views/app_style/app_color.dart'; class ProfilePage extends StatefulWidget { @@ -33,20 +33,28 @@ class ProfilePage extends StatefulWidget { class _ProfilePageState extends State { late UserProvider _userProvider; - late SettingProvider _settingProvider; - late double _width; - late double _height; + late DateTime _initialDate, _firstDate, _lastDate; + User _user = User(); bool _firstTime = true; final GlobalKey _formKey = GlobalKey(); final GlobalKey _scaffoldKey = GlobalKey(); + bool isTodayRota = true; + + @override + void initState() { + super.initState(); + _initialDate = DateTime.now(); + _firstDate = DateTime.utc(2010, 10, 16); + _lastDate = DateTime.utc(2030, 3, 14); + } @override Widget build(BuildContext context) { _userProvider = Provider.of(context); - _settingProvider = Provider.of(context); - _width = MediaQuery.of(context).size.width; - _height = MediaQuery.of(context).size.height; + // _settingProvider = Provider.of(context); + // _width = MediaQuery.of(context).size.width; + // _height = MediaQuery.of(context).size.height; if (_firstTime) { _user = User.fromJsonCons(_userProvider.user!.toJson()); @@ -165,8 +173,12 @@ class _ProfilePageState extends State { physics: const NeverScrollableScrollPhysics(), itemCount: _user.departmentName?.length ?? 0, shrinkWrap: true, + padding: EdgeInsets.zero, itemBuilder: (context, index) { - return (_user.departmentName![index] ?? "N/A").heading5(context).paddingOnly(top: 8, bottom: 8); + return Text( + _user.departmentName![index] ?? "N/A", + style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ).paddingOnly(top: 2, bottom: 2); }, ), if ((_user.clientName ?? "").isNotEmpty) ...[ @@ -219,105 +231,40 @@ class _ProfilePageState extends State { ], ).toShadowContainer(context), if (context.settingProvider.isUserFlowMedical) - FutureBuilder( - future: _userProvider.getUserRota(), - builder: (cxt, snapshot) { - bool isLoading = snapshot.connectionState == ConnectionState.waiting; - if ((!isLoading && (snapshot.hasError || !snapshot.hasData))) { - return const SizedBox(); - } - - return Container( - margin: const EdgeInsets.only(top: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Rota Details", - style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), + Container( + margin: const EdgeInsets.only(top: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Rota Details", + style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.black10), + ), + 8.width, + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: AppColor.primary10, ), - 8.height, - if (isLoading) ...[ - Text( - 'Shift Start Time Time', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ).toShimmer(context: context, isShow: isLoading), - 4.height, - Text( - 'Shift Actual Actual Start Time', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ).toShimmer(context: context, isShow: isLoading) - ] else - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - // Text( - // 'Site: ${_user.clientName ?? "-"}', - // style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - // ), - Text( - 'Shift Name: ${snapshot.data!.shiftName ?? "-"}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - 'Break: ${snapshot.data!.breakTime ?? "-"}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - if (snapshot.data!.leaveTypeCode?.isNotEmpty ?? false) - SizedBox( - width: double.infinity, - child: Column(children: [ - 8.height, - Text( - snapshot.data!.leaveTypeCode ?? "", - style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, fontWeight: FontWeight.w600), - ), - Text( - snapshot.data!.leaveDescription ?? "", - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - ]), - ) - else ...[ - Text( - 'Shift Start Time: ${snapshot.data!.shiftName?.split('-').first.trim() ?? "-"}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - 'Shift End Time: ${snapshot.data!.shiftName?.split('-').last.trim() ?? "-"}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - const Divider().defaultStyle(context), - Text( - 'Swipe In: ${snapshot.data!.shTActualStartTime?.toTime ?? "-"}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - Text( - 'Swipe Out: ${snapshot.data!.shTActualEndTime?.toTime ?? "-"}', - style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), - ), - ], - ], - ), - ], - ).toShadowContainer(context), - ); - - // if (!isDeviceSupported) { - // return Text("Your device did not support Fingerprint/Face ID.", style: AppTextStyles.tinyFont.copyWith(fontSize: 11, color: Colors.red)); - // } - - // return Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // "Fingerprint/Face ID".heading5(context), - // customSwitch(localAuth).onPress(() { - // checkForLocalAuth(localAuth); - // }), - // ], - // ); - }), + child: Text( + isTodayRota ? "Monthly " : "Today", + style: AppTextStyles.bodyText2.copyWith(color: Colors.white), + ), + ).onPress(() => setState(() { + _initialDate = DateTime.now(); + isTodayRota = !isTodayRota; + })), + ], + ), + (isTodayRota ? todayRotaWidget() : monthRotaWidget()) + ], + ).toShadowContainer(context), + ), ], ).expanded, FooterActionButton.footerContainer( @@ -348,4 +295,201 @@ class _ProfilePageState extends State { ), ); } + + Widget todayRotaWidget() { + final DateFormat fmt = DateFormat('MM-dd-yyyy'); + final String startStr = fmt.format(_initialDate); + + return FutureBuilder( + future: _userProvider.getUserTodayRota(startStr, startStr), + builder: (context, snapshot) { + bool isLoading = snapshot.connectionState == ConnectionState.waiting; + if ((!isLoading && (snapshot.hasError || !snapshot.hasData))) { + return const SizedBox(); + } + + return rotaDetailsWidget(isLoading, snapshot.data); + }, + ); + } + + Widget monthRotaWidget() { + final DateTime startOfMonth = DateTime(_initialDate.year, _initialDate.month, 1); + final DateTime endOfMonth = DateTime(_initialDate.year, _initialDate.month + 1, 0); + + final DateFormat fmt = DateFormat('MM-dd-yyyy'); + final String startStr = fmt.format(startOfMonth); + final String endStr = fmt.format(endOfMonth); + + return FutureBuilder>( + future: _userProvider.getUserMonthlyRota(startStr, endStr), + builder: (context, snapshot) { + bool isLoading = snapshot.connectionState == ConnectionState.waiting; + if ((!isLoading && (snapshot.hasError || !snapshot.hasData))) { + return const SizedBox(); + } + + return TableCalendar( + firstDay: _firstDate, + lastDay: _lastDate, + focusedDay: _initialDate, + calendarFormat: CalendarFormat.month, + weekendDays: const [], + rowHeight: 45, + pageJumpingEnabled: true, + pageAnimationEnabled: true, + availableGestures: AvailableGestures.none, + onPageChanged: (dateTime) { + _initialDate = dateTime; + setState(() {}); + }, + onDaySelected: (selectedDate, focusDate) { + final List matches = snapshot.data?.where((e) { + final dt = DateTime.tryParse(e.scheduleDate ?? ''); + return dt != null && isSameDay(dt, selectedDate); + }).toList() ?? + []; + + final UserRotaModel? rota = matches.isNotEmpty ? matches.first : null; + + if (rota == null) { + "No details found".showToast; + return; + } + + context.showBottomSheet(rotaDetailsWidget(isLoading, rota), title: "Rota Details".addTranslation); + }, + calendarBuilders: CalendarBuilders( + markerBuilder: (context, date, eventsOnDay) { + if (isLoading) return null; + final List matches = snapshot.data?.where((e) { + final dt = DateTime.tryParse(e.scheduleDate ?? ''); + return dt != null && isSameDay(dt, date); + }).toList() ?? + []; + + final UserRotaModel? rota = matches.isNotEmpty ? matches.first : null; + + if (rota == null) return null; + return Container( + width: 6, + height: 6, + margin: const EdgeInsets.symmetric(horizontal: 1, vertical: 4), + decoration: BoxDecoration( + color: rota.leaveTypeCode == "ANNUAL" + ? AppColor.red30 + : rota.leaveTypeCode == "HOLIDAY" + ? AppColor.green15 + : AppColor.primary10, + shape: BoxShape.circle), + ); + }, + dowBuilder: (context, dateTime) { + final day = DateFormat("EE", context.isAr ? "ar" : "en").format(dateTime).toUpperCase(); + return Align(alignment: Alignment.center, child: day.bodyText(context).custom(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50)); + }, + defaultBuilder: (_cxt, dateTime, _) { + final day = DateFormat("d").format(dateTime); + if (isLoading) { + return const SizedBox(height: 36, width: 36).toShimmer(isShow: isLoading, context: _cxt); + } else { + null; + } + }, + ), + // eventLoader: (day) { + // print("eventLoader:$day"); + // return []; + // }, + daysOfWeekHeight: 35.toScreenHeight, + headerStyle: HeaderStyle( + leftChevronVisible: true, + rightChevronVisible: true, + formatButtonVisible: false, + titleCentered: true, + titleTextStyle: AppTextStyles.bodyText, + headerPadding: const EdgeInsets.all(0), + ), + calendarStyle: CalendarStyle( + isTodayHighlighted: false, + todayDecoration: const BoxDecoration(color: AppColor.primary10, shape: BoxShape.circle), + defaultTextStyle: AppTextStyles.bodyText, + defaultDecoration: BoxDecoration(shape: BoxShape.circle, color: context.isDark ? AppColor.neutral50 : AppColor.neutral30), + ), + ).paddingOnly(top: 16); + }, + ); + } + + Widget rotaDetailsWidget(bool isLoading, UserRotaModel? userRota) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 16.height, + if (isLoading) ...[ + Text( + 'Shift Start Time Time', + style: AppTextStyles.bodyText.copyWith(color: context.isDarkNotListen ? AppColor.neutral10 : AppColor.neutral120), + ).toShimmer(context: context, isShow: isLoading), + 4.height, + Text( + 'Shift Actual Actual Start Time', + style: AppTextStyles.bodyText.copyWith(color: context.isDarkNotListen ? AppColor.neutral10 : AppColor.neutral120), + ).toShimmer(context: context, isShow: isLoading) + ] else + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // Text( + // 'Site: ${_user.clientName ?? "-"}', + // style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + // ), + Text( + 'Shift Name: ${userRota!.shiftName ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDarkNotListen ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + 'Break: ${userRota.breakTime ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDarkNotListen ? AppColor.neutral10 : AppColor.neutral120), + ), + if (userRota.leaveTypeCode?.isNotEmpty ?? false) + SizedBox( + width: double.infinity, + child: Column(children: [ + 8.height, + Text( + userRota.leaveTypeCode ?? "", + style: AppTextStyles.heading6.copyWith(color: context.isDarkNotListen ? AppColor.neutral10 : AppColor.neutral120, fontWeight: FontWeight.w600), + ), + Text( + userRota.leaveDescription ?? "", + style: AppTextStyles.bodyText.copyWith(color: context.isDarkNotListen ? AppColor.neutral10 : AppColor.neutral120), + ), + ]), + ) + else ...[ + Text( + 'Shift Start Time: ${userRota.shiftName?.split('-').first.trim() ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDarkNotListen ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + 'Shift End Time: ${userRota.shiftName?.split('-').last.trim() ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDarkNotListen ? AppColor.neutral10 : AppColor.neutral120), + ), + const Divider().defaultStyle(context), + Text( + 'Swipe In: ${userRota.shTActualStartTime?.toTime ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDarkNotListen ? AppColor.neutral10 : AppColor.neutral120), + ), + Text( + 'Swipe Out: ${userRota.shTActualEndTime?.toTime ?? "-"}', + style: AppTextStyles.bodyText.copyWith(color: context.isDarkNotListen ? AppColor.neutral10 : AppColor.neutral120), + ), + ], + ], + ), + ], + ); + } }