From 858fb48f68bc016c649bb85d08db47a8b8afc8ad Mon Sep 17 00:00:00 2001 From: "mr.mojtaba" Date: Sun, 2 Nov 2025 15:45:31 +0330 Subject: [PATCH] feat: new date picker and new logic --- android/local.properties | 2 +- .../pages/common/profile/view.dart | 154 +++---- .../steward/sales_in_province/logic.dart | 69 ++- .../pages/steward/sales_in_province/view.dart | 224 ++++++--- .../steward/sales_out_of_province/logic.dart | 76 +++- .../steward/sales_out_of_province/view.dart | 145 ++++-- .../pages/steward/segmentation/logic.dart | 87 +++- .../pages/steward/segmentation/view.dart | 128 ++++-- .../presentation/widget/monthly_calender.dart | 427 ++++++++++++++++++ .../core/lib/presentation/widget/widget.dart | 1 + .../lib/utils/network/safe_call_utils.dart | 6 +- pubspec.lock | 12 +- pubspec.yaml | 6 +- 13 files changed, 1090 insertions(+), 247 deletions(-) create mode 100644 packages/core/lib/presentation/widget/monthly_calender.dart diff --git a/android/local.properties b/android/local.properties index 5cbba42..7a96e05 100644 --- a/android/local.properties +++ b/android/local.properties @@ -1,5 +1,5 @@ sdk.dir=C:/Users/Housh11/AppData/Local/Android/Sdk flutter.sdk=C:\\src\\flutter -flutter.buildMode=release +flutter.buildMode=debug flutter.versionName=1.3.28 flutter.versionCode=25 \ No newline at end of file diff --git a/packages/chicken/lib/presentation/pages/common/profile/view.dart b/packages/chicken/lib/presentation/pages/common/profile/view.dart index d9aa3f8..6db6aa2 100644 --- a/packages/chicken/lib/presentation/pages/common/profile/view.dart +++ b/packages/chicken/lib/presentation/pages/common/profile/view.dart @@ -69,21 +69,21 @@ class ProfilePage extends GetView { ), Expanded( flex: 3, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: SingleChildScrollView( + physics: BouncingScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + rolesWidget(), + SizedBox(height: 12.h), - children: [ - rolesWidget(), - SizedBox(height: 12.h), - - ObxValue((data) { - if (data.value.status == ResourceStatus.loading) { - return LoadingWidget(); - } else if (data.value.status == ResourceStatus.error) { - return ErrorWidget('خطا در دریافت اطلاعات کاربر'); - } else if (data.value.status == ResourceStatus.success) { - return SingleChildScrollView( - child: Column( + ObxValue((data) { + if (data.value.status == ResourceStatus.loading) { + return LoadingWidget(); + } else if (data.value.status == ResourceStatus.error) { + return ErrorWidget('خطا در دریافت اطلاعات کاربر'); + } else if (data.value.status == ResourceStatus.success) { + return Column( spacing: 6, children: [ ObxValue((isOpen) { @@ -122,75 +122,75 @@ class ProfilePage extends GetView { }, controller.isUnitInformationOpen), ), ], + ); + } else { + return SizedBox.shrink(); + } + }, controller.userProfile), + GestureDetector( + onTap: () { + Get.bottomSheet(changePasswordBottomSheet(), isScrollControlled: true); + }, + child: Container( + height: 47.h, + margin: EdgeInsets.symmetric(horizontal: 8, vertical: 8.h), + padding: EdgeInsets.symmetric(horizontal: 11.h, vertical: 8.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(width: 1, color: const Color(0xFFD6D6D6)), + ), + child: Row( + spacing: 6, + children: [ + Assets.vec.lockSvg.svg( + width: 24.w, + height: 24.h, + colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn), + ), + Text( + 'تغییر رمز عبور', + textAlign: TextAlign.center, + style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal), + ), + ], ), - ); - } else { - return SizedBox.shrink(); - } - }, controller.userProfile), - GestureDetector( - onTap: () { - Get.bottomSheet(changePasswordBottomSheet(), isScrollControlled: true); - }, - child: Container( - height: 47.h, - margin: EdgeInsets.symmetric(horizontal: 8, vertical: 8.h), - padding: EdgeInsets.symmetric(horizontal: 11.h, vertical: 8.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - border: Border.all(width: 1, color: const Color(0xFFD6D6D6)), - ), - child: Row( - spacing: 6, - children: [ - Assets.vec.lockSvg.svg( - width: 24.w, - height: 24.h, - colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn), - ), - Text( - 'تغییر رمز عبور', - textAlign: TextAlign.center, - style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal), - ), - ], ), ), - ), - GestureDetector( - onTap: () { - Get.bottomSheet(exitBottomSheet(), isScrollControlled: true); - }, - child: Container( - height: 47.h, - margin: EdgeInsets.symmetric(horizontal: 8), - padding: EdgeInsets.symmetric(horizontal: 11.h, vertical: 8.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - border: Border.all(width: 1, color: const Color(0xFFD6D6D6)), - ), - child: Row( - spacing: 6, - children: [ - Assets.vec.logoutSvg.svg( - width: 24.w, - height: 24.h, - colorFilter: ColorFilter.mode(AppColor.redNormal, BlendMode.srcIn), - ), - Text( - 'خروج', - textAlign: TextAlign.center, - style: AppFonts.yekan14.copyWith(color: AppColor.redNormal), - ), - ], + GestureDetector( + onTap: () { + Get.bottomSheet(exitBottomSheet(), isScrollControlled: true); + }, + child: Container( + height: 47.h, + margin: EdgeInsets.symmetric(horizontal: 8), + padding: EdgeInsets.symmetric(horizontal: 11.h, vertical: 8.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(width: 1, color: const Color(0xFFD6D6D6)), + ), + child: Row( + spacing: 6, + children: [ + Assets.vec.logoutSvg.svg( + width: 24.w, + height: 24.h, + colorFilter: ColorFilter.mode(AppColor.redNormal, BlendMode.srcIn), + ), + Text( + 'خروج', + textAlign: TextAlign.center, + style: AppFonts.yekan14.copyWith(color: AppColor.redNormal), + ), + ], + ), ), ), - ), - SizedBox(height: 100), - ], + SizedBox(height: 100), + ], + ), ), ), ], diff --git a/packages/chicken/lib/presentation/pages/steward/sales_in_province/logic.dart b/packages/chicken/lib/presentation/pages/steward/sales_in_province/logic.dart index c42a247..6165b6c 100644 --- a/packages/chicken/lib/presentation/pages/steward/sales_in_province/logic.dart +++ b/packages/chicken/lib/presentation/pages/steward/sales_in_province/logic.dart @@ -35,12 +35,11 @@ class SalesInProvinceLogic extends GetxController { Rx fromDateFilter = Jalali.now().obs; Rx toDateFilter = Jalali.now().obs; - Rxn productionData = Rxn(); Rxn selectedProductModel = Rxn(); Rxn selectedGuildModel = Rxn(); Rxn guildProfile = Rxn(); RxInt saleType = 1.obs; - RxInt priceType = 1.obs; + RxInt priceType = 2.obs; RxInt quotaType = 1.obs; RxInt weight = 0.obs; RxInt pricePerKilo = 0.obs; @@ -59,6 +58,10 @@ class SalesInProvinceLogic extends GetxController { Rxn selectedAllocationModelForUpdate = Rxn(); SubmitStewardAllocation? tmpStewardAllocation; + Rxn productionDate = Rxn(null); + Map freeProductionDateData = {}; + Map governmentalProductionDateData = {}; + @override void onInit() { super.onInit(); @@ -97,6 +100,7 @@ class SalesInProvinceLogic extends GetxController { totalCost, selectedProductModel, selectedGuildModel, + productionDate, ], (callback) => checkVerification()); scrollControllerAllocationsMade.addListener(() { @@ -112,6 +116,45 @@ class SalesInProvinceLogic extends GetxController { (callback) => getAllocatedMade(), time: Duration(milliseconds: timeDebounce), ); + + _updateGovernmentalProductionDateData(); + _updateFreeProductionDateData(); + ever(rootLogic.stewardSalesInfoDashboard, (callback) { + _updateGovernmentalProductionDateData(); + _updateFreeProductionDateData(); + }); + } + + void _updateGovernmentalProductionDateData() { + var today = Jalali.now(); + governmentalProductionDateData = { + today.formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalGovernmentalRemainWeight?.toInt(), + ), + + today.addDays(-1).formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalGovernmentalRemainWeight?.toInt(), + ), + + today.addDays(-2).formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalGovernmentalRemainWeight?.toInt(), + ), + }; + } + + void _updateFreeProductionDateData() { + var today = Jalali.now(); + freeProductionDateData = { + today.formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalFreeRemainWeight?.toInt(), + ), + today.addDays(-1).formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalFreeRemainWeight?.toInt(), + ), + today.addDays(-2).formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalFreeRemainWeight?.toInt(), + ), + }; } Future getAllocatedMade([bool isLoadingMore = false]) async { @@ -168,10 +211,18 @@ class SalesInProvinceLogic extends GetxController { } void checkVerification() { + var hasWeight = quotaType.value == 1 + ? weight.value <= + (governmentalProductionDateData[productionDate.value?.formatCompactDate()]?.value ?? + 0) + : weight.value <= + (freeProductionDateData[productionDate.value?.formatCompactDate()]?.value ?? 0); + isValid.value = weight.value > 0 && pricePerKilo.value > 0 && totalCost.value > 0 && + hasWeight && selectedProductModel.value != null && selectedGuildModel.value != null; } @@ -285,7 +336,7 @@ class SalesInProvinceLogic extends GetxController { weightOfCarcasses: weight.value, sellType: saleType.value == 2 ? "free" : 'exclusive', numberOfCarcasses: 0, - productionDate: productionData.value?.toDateTime().formattedDashedGregorian, + productionDate: productionDate.value?.toDateTime().formattedDashedGregorian, quota: quotaType.value == 1 ? 'governmental' : 'free', guildKey: selectedGuildModel.value?.key, productKey: selectedProductModel.value?.key, @@ -308,6 +359,8 @@ class SalesInProvinceLogic extends GetxController { clearForm(); onRefresh(); rootLogic.onRefresh(); + Future.delayed(Duration(seconds: 1), () => defaultShowSuccessMessage("ثبت موفق بود")); + Get.back(); }, onError: (error, stackTrace) {}, ); @@ -345,7 +398,7 @@ class SalesInProvinceLogic extends GetxController { pricePerKiloController.text = pricePerKilo.value.toString().separatedByComma; totalCostController.text = totalCost.value.toString().separatedByComma; isValid.value = true; - productionData.value = item.productionDate.toJalali; + productionDate.value = item.productionDate.toJalali; } void clearForm() { @@ -359,7 +412,10 @@ class SalesInProvinceLogic extends GetxController { } totalCostController.clear(); isValid.value = false; - productionData.value = null; + productionDate.value = null; + quotaType.value = 1; + priceType.value = 2; + saleType.value = 2; } Future updateAllocation() async { @@ -382,6 +438,8 @@ class SalesInProvinceLogic extends GetxController { clearForm(); onRefresh(); rootLogic.onRefresh(); + Future.delayed(Duration(seconds: 1), () => defaultShowSuccessMessage("ویرایش موفق بود")); + Get.back(); }, onError: (error, stackTrace) {}, ); @@ -427,6 +485,7 @@ class SalesInProvinceLogic extends GetxController { if (broadcastPrice.value?.active == true) { pricePerKilo.value = broadcastPrice.value?.stewardPrice ?? 0; pricePerKiloController.text = pricePerKilo.value.toString().separatedByComma; + priceType.value = 2; } }, onError: (error, stacktrace) {}, diff --git a/packages/chicken/lib/presentation/pages/steward/sales_in_province/view.dart b/packages/chicken/lib/presentation/pages/steward/sales_in_province/view.dart index e35e618..565e27b 100644 --- a/packages/chicken/lib/presentation/pages/steward/sales_in_province/view.dart +++ b/packages/chicken/lib/presentation/pages/steward/sales_in_province/view.dart @@ -404,7 +404,7 @@ class SalesInProvincePage extends GetView { Widget addOrEditBottomSheet([bool isEditMode = false]) { return BaseBottomSheet( - height: Get.height * (isEditMode ? 0.55 : 0.75), + height: Get.height * (isEditMode ? 0.60 : 0.75), child: Form( key: controller.formKey, child: Column( @@ -528,32 +528,25 @@ class SalesInProvincePage extends GetView { label: 'وزن لاشه (کیلوگرم)', ), - ObxValue((data) { - return RTextField( - controller: TextEditingController(), - filledColor: AppColor.bgLight, - filled: true, + Obx(() { + return MonthlyDataCalendar( label: 'تاریخ تولید گوشت', - onTap: () { - Get.bottomSheet( - modalDatePicker((value) { - controller.productionData.value = value; - controller.productionData.refresh(); - }), - ); + selectedDate: controller.productionDate.value?.formatCompactDate(), + onDateSelect: (value) { + controller.productionDate.value = value.date; }, - borderColor: AppColor.darkGreyLight, - initText: data.value?.formatCompactDate(), + dayData: controller.quotaType.value == 1 + ? controller.governmentalProductionDateData + : controller.freeProductionDateData, ); - }, controller.productionData), + }), Visibility( visible: isEditMode == false, child: Column( - spacing: 12, children: [ - SizedBox(height: 12.h), + SizedBox(height: 12), Container( - height: 58.h, + height: 50.h, clipBehavior: Clip.none, decoration: BoxDecoration( color: Colors.white, @@ -574,26 +567,102 @@ class SalesInProvincePage extends GetView { return RadioGroup( groupValue: controller.quotaType.value, onChanged: (value) { - controller.quotaType.value = value!; + controller.quotaType.value = value ?? 0; }, child: Row( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, children: [ Expanded( - child: Row( - children: [ - Radio(value: 1), - Text('انبار دولتی', style: AppFonts.yekan14), - ], + child: GestureDetector( + onTap: + (controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalGovernmentalRemainWeight ?? + -1) > + 0 + ? () { + controller.quotaType.value = 1; + } + : null, + child: Row( + children: [ + Radio( + value: 1, + enabled: + (controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalGovernmentalRemainWeight ?? + -1) > + 0, + ), + Text( + 'انبار دولتی', + style: AppFonts.yekan14.copyWith( + color: + ((controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalGovernmentalRemainWeight ?? + -1) > + 0) + ? AppColor.textColor + : AppColor.labelTextColor, + ), + ), + ], + ), ), ), Expanded( - child: Row( - children: [ - Radio(value: 2), - Text('انبار آزاد', style: AppFonts.yekan14), - ], + child: GestureDetector( + onTap: + (controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalFreeRemainWeight ?? + -1) > + 0 + ? () { + controller.quotaType.value = 2; + } + : null, + child: Row( + children: [ + Radio( + value: 2, + enabled: + (controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalFreeRemainWeight ?? + -1) > + 0, + ), + Text( + 'انبار آزاد', + style: AppFonts.yekan14.copyWith( + color: + ((controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalFreeRemainWeight ?? + -1) > + 0) + ? AppColor.textColor + : AppColor.labelTextColor, + ), + ), + ], + ), ), ), ], @@ -603,7 +672,7 @@ class SalesInProvincePage extends GetView { ], ), ), - SizedBox(height: 12.h), + SizedBox(height: 12), Container( height: 58.h, clipBehavior: Clip.none, @@ -631,20 +700,46 @@ class SalesInProvincePage extends GetView { child: Row( children: [ Expanded( - child: Row( - children: [ - Radio(value: 1), - Text('قیمت دولتی', style: AppFonts.yekan14), - ], + child: GestureDetector( + onTap: (controller.broadcastPrice.value?.active ?? false) + ? () { + controller.priceType.value = 1; + } + : null, + child: Row( + children: [ + Radio( + value: 1, + enabled: + controller.broadcastPrice.value?.active ?? + false, + ), + Text( + 'قیمت دولتی', + style: AppFonts.yekan14.copyWith( + color: + (controller.broadcastPrice.value?.active ?? + false) + ? AppColor.textColor + : AppColor.labelTextColor, + ), + ), + ], + ), ), ), Expanded( - child: Row( - children: [ - Radio(value: 2), - Text('قیمت آزاد', style: AppFonts.yekan14), - ], + child: GestureDetector( + onTap: () { + controller.priceType.value = 2; + }, + child: Row( + children: [ + Radio(value: 2), + Text('قیمت آزاد', style: AppFonts.yekan14), + ], + ), ), ), ], @@ -657,30 +752,25 @@ class SalesInProvincePage extends GetView { ], ), ), - Obx(() { - return Visibility( - visible: controller.broadcastPrice.value?.active == true, - child: RTextField( - variant: RTextFieldVariant.noBorder, - controller: controller.pricePerKiloController, - borderColor: AppColor.darkGreyLight, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - SeparatorInputFormatter(), - ], - filledColor: AppColor.bgLight, - filled: true, - enabled: - (controller.priceType.value == 2 || - (controller.selectedProductModel.value?.approvedPriceStatus == false)), - onChanged: (p0) { - controller.pricePerKilo.value = int.tryParse(p0.clearComma) ?? 0; - }, - keyboardType: TextInputType.number, - label: 'قیمت هر کیلو (ريال)', - ), - ); - }), + RTextField( + variant: RTextFieldVariant.noBorder, + controller: controller.pricePerKiloController, + borderColor: AppColor.darkGreyLight, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + SeparatorInputFormatter(), + ], + filledColor: AppColor.bgLight, + filled: true, + enabled: + (controller.priceType.value == 2 || + (controller.selectedProductModel.value?.approvedPriceStatus == false)), + onChanged: (p0) { + controller.pricePerKilo.value = int.tryParse(p0.clearComma) ?? 0; + }, + keyboardType: TextInputType.number, + label: 'قیمت هر کیلو (ريال)', + ), RTextField( variant: RTextFieldVariant.noBorder, @@ -711,11 +801,9 @@ class SalesInProvincePage extends GetView { onPressed: isEditMode ? () async { await controller.updateAllocation(); - Get.back(); } : () async { await controller.submitAllocation(); - Get.back(); }, ); }, controller.isValid), diff --git a/packages/chicken/lib/presentation/pages/steward/sales_out_of_province/logic.dart b/packages/chicken/lib/presentation/pages/steward/sales_out_of_province/logic.dart index 099d098..f2d5ad9 100644 --- a/packages/chicken/lib/presentation/pages/steward/sales_out_of_province/logic.dart +++ b/packages/chicken/lib/presentation/pages/steward/sales_out_of_province/logic.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:rasadyar_chicken/data/models/request/steward_free_sale_bar/steward_free_sale_bar_request.dart'; +import 'package:rasadyar_chicken/data/models/response/broadcast_price/broadcast_price.dart'; import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart'; import 'package:rasadyar_chicken/data/models/response/out_province_carcasses_buyer/out_province_carcasses_buyer.dart'; import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart'; @@ -30,7 +31,7 @@ class SalesOutOfProvinceLogic extends GetxController { RxList routesName = RxList(); RxBool isLoadingMoreAllocationsMade = false.obs; Rxn selectedCity = Rxn(); - Rxn productionData = Rxn(); + Rxn broadcastPrice = Rxn(); GlobalKey formKey = GlobalKey(); TextEditingController quarantineCodeController = TextEditingController(); TextEditingController saleWeightController = TextEditingController(); @@ -46,6 +47,9 @@ class SalesOutOfProvinceLogic extends GetxController { RxInt saleType = 2.obs; RxInt quotaType = 1.obs; + Rxn productionDate = Rxn(); + Map freeProductionDateData = {}; + Map governmentalProductionDateData = {}; @override void onInit() { @@ -57,6 +61,7 @@ class SalesOutOfProvinceLogic extends GetxController { void onReady() { super.onReady(); getOutProvinceSales(); + getBroadcastPrice(); selectedProduct.value = rootLogic.rolesProductsModel.first; debounce( searchedValue, @@ -64,6 +69,46 @@ class SalesOutOfProvinceLogic extends GetxController { time: Duration(milliseconds: timeDebounce), ); setupListeners(); + + _updateGovernmentalProductionDateData(); + _updateFreeProductionDateData(); + ever(rootLogic.stewardSalesInfoDashboard, (callback) { + _updateGovernmentalProductionDateData(); + _updateFreeProductionDateData(); + }); + } + + void _updateGovernmentalProductionDateData() { + var today = Jalali.now(); + + governmentalProductionDateData = { + today.formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalGovernmentalRemainWeight?.toInt(), + ), + + today.addDays(-1).formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalGovernmentalRemainWeight?.toInt(), + ), + + today.addDays(-2).formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalGovernmentalRemainWeight?.toInt(), + ), + }; + } + + void _updateFreeProductionDateData() { + var today = Jalali.now(); + freeProductionDateData = { + today.formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalFreeRemainWeight?.toInt(), + ), + today.addDays(-1).formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalFreeRemainWeight?.toInt(), + ), + today.addDays(-2).formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalFreeRemainWeight?.toInt(), + ), + }; } void setSearchValue(String? value) { @@ -151,7 +196,7 @@ class SalesOutOfProvinceLogic extends GetxController { saleType.value = item.saleType == 'free' ? 2 : 1; quotaType.value = item.quota == 'governmental' ? 1 : 2; isSaleSubmitButtonEnabled.value = true; - productionData.value = item.productionDate.toJalali; + productionDate.value = item.productionDate.toJalali; } Future deleteStewardPurchaseOutOfProvince(String key) async { @@ -174,6 +219,7 @@ class SalesOutOfProvinceLogic extends GetxController { productKey: selectedProduct.value?.key, saleType: saleType.value == 2 ? 'free' : 'exclusive', quota: quotaType.value == 1 ? 'governmental' : 'free', + productionDate: productionDate.value?.toDateTime().formattedDashedGregorian, ); await safeCall( showError: true, @@ -185,6 +231,11 @@ class SalesOutOfProvinceLogic extends GetxController { res = true; onRefresh(); rootLogic.onRefresh(); + Future.delayed( + Duration(seconds: 1), + () => defaultShowSuccessMessage("عملیات با موفقیت انجام شد"), + ); + Get.back(); }, ); return res; @@ -194,9 +245,9 @@ class SalesOutOfProvinceLogic extends GetxController { quarantineCodeController.clear(); saleWeightController.clear(); saleDate.value = Jalali.now(); - productionData.value = null; + productionDate.value = null; saleType.value = 2; - quotaType.value = 1; + quotaType.value = 2; selectedBuyer.value = null; } @@ -219,6 +270,11 @@ class SalesOutOfProvinceLogic extends GetxController { onSuccess: (_) { res = true; onRefresh(); + Future.delayed( + Duration(seconds: 1), + () => defaultShowSuccessMessage("عملیات با موفقیت انجام شد"), + ); + Get.back(); }, ); return res; @@ -246,4 +302,16 @@ class SalesOutOfProvinceLogic extends GetxController { expandedListIndex.value = index; } } + + Future getBroadcastPrice() async { + safeCall( + call: () async => await rootLogic.chickenRepository.getBroadcastPrice( + token: rootLogic.tokenService.accessToken.value!, + ), + onSuccess: (result) { + broadcastPrice.value = result; + }, + onError: (error, stacktrace) {}, + ); + } } diff --git a/packages/chicken/lib/presentation/pages/steward/sales_out_of_province/view.dart b/packages/chicken/lib/presentation/pages/steward/sales_out_of_province/view.dart index 28f12d9..f4a963b 100644 --- a/packages/chicken/lib/presentation/pages/steward/sales_out_of_province/view.dart +++ b/packages/chicken/lib/presentation/pages/steward/sales_out_of_province/view.dart @@ -324,7 +324,7 @@ class SalesOutOfProvincePage extends GetView { Widget addOrEditSaleBottomSheet([bool isOnEdit = false]) { return BaseBottomSheet( - height: 600.h, + height: 670.h, child: SingleChildScrollView( child: Form( key: controller.formKey, @@ -357,13 +357,12 @@ class SalesOutOfProvincePage extends GetView { Get.bottomSheet( modalDatePicker((value) { controller.saleDate.value = value; - controller.saleDate.refresh(); }), ); }, borderColor: AppColor.darkGreyLight, - initText: data.value?.formatCompactDate(), + initText: data.value.formatCompactDate(), ); }, controller.saleDate), Visibility(visible: isOnEdit == false, child: _buyerWidget()), @@ -386,24 +385,18 @@ class SalesOutOfProvincePage extends GetView { child: Column( spacing: 12, children: [ - ObxValue((data) { - return RTextField( - controller: TextEditingController(), - filledColor: AppColor.bgLight, - filled: true, + Obx(() { + return MonthlyDataCalendar( label: 'تاریخ تولید گوشت', - onTap: () { - Get.bottomSheet( - modalDatePicker((value) { - controller.productionData.value = value; - controller.productionData.refresh(); - }), - ); + selectedDate: controller.productionDate.value?.formatCompactDate(), + onDateSelect: (value) { + controller.productionDate.value = value.date; }, - borderColor: AppColor.darkGreyLight, - initText: data.value?.formatCompactDate(), + dayData: controller.quotaType.value == 1 + ? controller.governmentalProductionDateData + : controller.freeProductionDateData, ); - }, controller.productionData), + }), Container( height: 50.h, @@ -434,13 +427,46 @@ class SalesOutOfProvincePage extends GetView { children: [ Expanded( child: GestureDetector( - onTap: () { - controller.quotaType.value = 1; - }, + onTap: + (controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalGovernmentalRemainWeight ?? + -1) > + 0 + ? () { + controller.quotaType.value = 1; + } + : null, child: Row( children: [ - Radio(value: 1), - Text('انبار دولتی', style: AppFonts.yekan14), + Radio( + value: 1, + enabled: + (controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalGovernmentalRemainWeight ?? + -1) > + 0, + ), + Text( + 'انبار دولتی', + style: AppFonts.yekan14.copyWith( + color: + ((controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalGovernmentalRemainWeight ?? + -1) > + 0) + ? AppColor.textColor + : AppColor.labelTextColor, + ), + ), ], ), ), @@ -448,13 +474,46 @@ class SalesOutOfProvincePage extends GetView { Expanded( child: GestureDetector( - onTap: () { - controller.quotaType.value = 2; - }, + onTap: + (controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalFreeRemainWeight ?? + -1) > + 0 + ? () { + controller.quotaType.value = 2; + } + : null, child: Row( children: [ - Radio(value: 2), - Text('انبار آزاد', style: AppFonts.yekan14), + Radio( + value: 2, + enabled: + (controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalFreeRemainWeight ?? + -1) > + 0, + ), + Text( + 'انبار آزاد', + style: AppFonts.yekan14.copyWith( + color: + ((controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalFreeRemainWeight ?? + -1) > + 0) + ? AppColor.textColor + : AppColor.labelTextColor, + ), + ), ], ), ), @@ -496,13 +555,30 @@ class SalesOutOfProvincePage extends GetView { children: [ Expanded( child: GestureDetector( - onTap: () { - controller.saleType.value = 1; - }, + onTap: + (controller.broadcastPrice.value?.active ?? false) + ? () { + controller.saleType.value = 1; + } + : null, child: Row( children: [ - Radio(value: 1), - Text('قیمت دولتی', style: AppFonts.yekan14), + Radio( + value: 1, + enabled: + (controller.broadcastPrice.value?.active ?? + false), + ), + Text( + 'قیمت دولتی', + style: AppFonts.yekan14.copyWith( + color: + (controller.broadcastPrice.value?.active ?? + false) + ? AppColor.textColor + : AppColor.labelTextColor, + ), + ), ], ), ), @@ -594,9 +670,6 @@ class SalesOutOfProvincePage extends GetView { onPressed: data.value ? () async { var res = isOnEdit ? await controller.editSale() : await controller.createSale(); - if (res) { - Get.back(); - } } : null, height: 40, diff --git a/packages/chicken/lib/presentation/pages/steward/segmentation/logic.dart b/packages/chicken/lib/presentation/pages/steward/segmentation/logic.dart index 5026d2c..96453c1 100644 --- a/packages/chicken/lib/presentation/pages/steward/segmentation/logic.dart +++ b/packages/chicken/lib/presentation/pages/steward/segmentation/logic.dart @@ -23,7 +23,7 @@ class SegmentationLogic extends GetxController { RxnString searchedValue = RxnString(); RxInt segmentType = 1.obs; RxInt saleType = 2.obs; - RxInt quotaType = 1.obs; + RxInt quotaType = 2.obs; GlobalKey formKey = GlobalKey(); TextEditingController weightController = TextEditingController(text: '0'); RxBool isSubmitButtonEnabled = false.obs; @@ -35,9 +35,12 @@ class SegmentationLogic extends GetxController { Resource>.loading().obs; RxList guildsModel = [].obs; - Rxn productionData = Rxn(); Rx saleDate = Jalali.now().obs; + Rxn productionDate = Rxn(null); + Map freeProductionDateData = {}; + Map governmentalProductionDateData = {}; + @override void onInit() { super.onInit(); @@ -45,6 +48,46 @@ class SegmentationLogic extends GetxController { once(rootLogic.rolesProductsModel, (callback) => selectedProduct.value = callback.first); getAllSegmentation(); getGuilds(); + + _updateGovernmentalProductionDateData(); + _updateFreeProductionDateData(); + + ever(rootLogic.stewardSalesInfoDashboard, (callback) { + _updateGovernmentalProductionDateData(); + _updateFreeProductionDateData(); + }); + } + + void _updateGovernmentalProductionDateData() { + var today = Jalali.now(); + governmentalProductionDateData = { + today.formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalGovernmentalRemainWeight?.toInt(), + ), + + today.addDays(-1).formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalGovernmentalRemainWeight?.toInt(), + ), + + today.addDays(-2).formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalGovernmentalRemainWeight?.toInt(), + ), + }; + } + + void _updateFreeProductionDateData() { + var today = Jalali.now(); + freeProductionDateData = { + today.formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalFreeRemainWeight?.toInt(), + ), + today.addDays(-1).formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalFreeRemainWeight?.toInt(), + ), + today.addDays(-2).formatCompactDate(): DayData( + value: rootLogic.stewardSalesInfoDashboard.value?.totalFreeRemainWeight?.toInt(), + ), + }; } @override @@ -55,7 +98,6 @@ class SegmentationLogic extends GetxController { @override void onClose() { - // TODO: implement onClose super.onClose(); } @@ -69,7 +111,8 @@ class SegmentationLogic extends GetxController { (callback) => getAllSegmentation(), time: Duration(milliseconds: timeDebounce), ); - ever(selectedSegment, (_) { + + everAll([selectedSegment, quotaType, saleType], (_) { validateForm(); }); @@ -85,17 +128,26 @@ class SegmentationLogic extends GetxController { weightController.text = '0'; selectedSegment.value = null; selectedGuildModel.value = null; - productionData.value = null; + productionDate.value = Jalali.now(); segmentType.value = 1; - saleType.value = 1; + saleType.value = 2; quotaType.value = 1; } void validateForm() { var weight = int.tryParse(weightController.text.trim().clearComma); + var hasWeight = quotaType.value == 2 + ? ((weight ?? 0) <= (rootLogic.stewardSalesInfoDashboard.value?.totalFreeRemainWeight ?? 0)) + : ((weight ?? 0) <= + (rootLogic.stewardSalesInfoDashboard.value?.totalGovernmentalRemainWeight ?? 0)); + if (!hasWeight) { + defaultShowErrorMessage("میزان وزن تخیصصی شده بیشتر از وزن باقیمانده است!"); + } + isSubmitButtonEnabled.value = selectedProduct.value != null && weightController.text.isNotEmpty && + hasWeight && weight! > 0 && (segmentType.value == 1 || (segmentType.value == 2 && selectedGuildModel.value != null)); } @@ -115,7 +167,6 @@ class SegmentationLogic extends GetxController { await safeCall( showError: true, - showSuccess: true, call: () async => await rootLogic.chickenRepository.getSegmentation( token: rootLogic.tokenService.accessToken.value!, queryParameters: buildQueryParams( @@ -171,7 +222,7 @@ class SegmentationLogic extends GetxController { model: SegmentationModel( key: selectedSegment.value?.key, weight: int.tryParse(weightController.text.clearComma) ?? 0, - productionDate: productionData.value?.toDateTime().formattedDashedGregorian, + productionDate: productionDate.value?.toDateTime().formattedDashedGregorian, ), ), onSuccess: (result) { @@ -196,22 +247,24 @@ class SegmentationLogic extends GetxController { if (segmentType.value == 2) { segmentationModel = segmentationModel.copyWith(guildKey: selectedGuildModel.value?.key); } - if (productionData.value != null) { - segmentationModel = segmentationModel.copyWith( - productionDate: productionData.value?.toDateTime().formattedDashedGregorian, - ); - } + segmentationModel = segmentationModel.copyWith( + productionDate: productionDate.value?.toDateTime().formattedDashedGregorian, + ); await safeCall( showError: true, - showSuccess: true, call: () async => await rootLogic.chickenRepository.createSegmentation( token: rootLogic.tokenService.accessToken.value!, model: segmentationModel, ), - onSuccess: (result) { + onSuccess: (result) async { res = true; - isSubmitButtonEnabled.value = true; + isSubmitButtonEnabled.value = false; onRefresh(); + Future.delayed( + Duration(seconds: 1), + () => defaultShowSuccessMessage("قطعه‌بندی با موفقیت ثبت شد!"), + ); + Get.back(); }, onError: (error, stacktrace) { res = false; @@ -241,6 +294,8 @@ class SegmentationLogic extends GetxController { currentPage.value = 1; await rootLogic.onRefresh(); await getAllSegmentation(); + _updateFreeProductionDateData(); + _updateGovernmentalProductionDateData(); } void toggleExpansion({int? index}) { diff --git a/packages/chicken/lib/presentation/pages/steward/segmentation/view.dart b/packages/chicken/lib/presentation/pages/steward/segmentation/view.dart index 6d16512..54d33ba 100644 --- a/packages/chicken/lib/presentation/pages/steward/segmentation/view.dart +++ b/packages/chicken/lib/presentation/pages/steward/segmentation/view.dart @@ -10,6 +10,10 @@ import 'package:rasadyar_core/core.dart'; import 'logic.dart'; class SegmentationPage extends GetView { + final today = Jalali.now(); + final oneDayAgo = Jalali.now().addDays(-1); + final twoDaysAgo = Jalali.now().addDays(-2); + @override Widget build(BuildContext context) { return ChickenBasePage( @@ -342,24 +346,6 @@ class SegmentationPage extends GetView { ], ), ), - ObxValue((data) { - return RTextField( - controller: TextEditingController(), - filledColor: AppColor.bgLight, - filled: true, - label: 'تاریخ تولید گوشت', - onTap: () { - Get.bottomSheet( - modalDatePicker((value) { - controller.productionData.value = value; - controller.productionData.refresh(); - }), - ); - }, - borderColor: AppColor.darkGreyLight, - initText: data.value?.formatCompactDate(), - ); - }, controller.productionData), Container( height: 50.h, @@ -458,13 +444,46 @@ class SegmentationPage extends GetView { children: [ Expanded( child: GestureDetector( - onTap: () { - controller.quotaType.value = 1; - }, + onTap: + (controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalGovernmentalRemainWeight ?? + -1) > + 0 + ? () { + controller.quotaType.value = 1; + } + : null, child: Row( children: [ - Radio(value: 1), - Text('انبار دولتی', style: AppFonts.yekan14), + Radio( + value: 1, + enabled: + (controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalGovernmentalRemainWeight ?? + -1) > + 0, + ), + Text( + 'انبار دولتی', + style: AppFonts.yekan14.copyWith( + color: + ((controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalGovernmentalRemainWeight ?? + -1) > + 0) + ? AppColor.textColor + : AppColor.labelTextColor, + ), + ), ], ), ), @@ -472,13 +491,46 @@ class SegmentationPage extends GetView { Expanded( child: GestureDetector( - onTap: () { - controller.quotaType.value = 2; - }, + onTap: + (controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalFreeRemainWeight ?? + -1) > + 0 + ? () { + controller.quotaType.value = 2; + } + : null, child: Row( children: [ - Radio(value: 2), - Text('انبار آزاد', style: AppFonts.yekan14), + Radio( + value: 2, + enabled: + (controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalFreeRemainWeight ?? + -1) > + 0, + ), + Text( + 'انبار آزاد', + style: AppFonts.yekan14.copyWith( + color: + ((controller + .rootLogic + .stewardSalesInfoDashboard + .value + ?.totalFreeRemainWeight ?? + -1) > + 0) + ? AppColor.textColor + : AppColor.labelTextColor, + ), + ), ], ), ), @@ -490,6 +542,17 @@ class SegmentationPage extends GetView { ], ), ), + + Obx(() { + return MonthlyDataCalendar( + label: 'تاریخ تولید گوشت', + selectedDate: controller.productionDate.value?.formatCompactDate(), + onDateSelect: (value) => controller.productionDate..value = value.date, + dayData: controller.quotaType.value == 1 + ? controller.governmentalProductionDateData + : controller.freeProductionDateData, + ); + }), ], ), ), @@ -609,6 +672,7 @@ class SegmentationPage extends GetView { } Container modalDatePicker(ValueChanged onDateSelected) { + Jalali currentDate = Jalali.now(); Jalali? tempPickedDate; return Container( height: 250, @@ -652,6 +716,14 @@ class SegmentationPage extends GetView { child: PersianCupertinoDatePicker( initialDateTime: controller.saleDate.value, mode: PersianCupertinoDatePickerMode.date, + maximumDate: currentDate.addDays(3), + minimumDate: currentDate + .toDateTime() + .subtract(Duration(days: 1)) + .toString() + .toJalali, + maximumYear: currentDate.year, + minimumYear: currentDate.year, onDateTimeChanged: (dateTime) { tempPickedDate = dateTime; }, diff --git a/packages/core/lib/presentation/widget/monthly_calender.dart b/packages/core/lib/presentation/widget/monthly_calender.dart new file mode 100644 index 0000000..7016f63 --- /dev/null +++ b/packages/core/lib/presentation/widget/monthly_calender.dart @@ -0,0 +1,427 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart' as intl; +import 'package:rasadyar_core/core.dart'; + +/// MonthlyDataCalendar - A Persian calendar date picker that displays data values under each day +/// Shows a full month grid with day numbers and associated data values +/// Only today and 2 days ago are enabled +class MonthlyDataCalendar extends StatefulWidget { + final Function(DayInfo)? onDateSelect; + final Map? dayData; // Map with date keys and data objects + final String? selectedDate; + final String label; + + const MonthlyDataCalendar({ + super.key, + this.onDateSelect, + this.dayData, + this.selectedDate, + this.label = 'انتخاب تاریخ', + }); + + @override + State createState() => _MonthlyDataCalendarState(); +} + +class _MonthlyDataCalendarState extends State { + late Jalali _currentMonth; + List _calendarDays = []; + final TextEditingController _textController = TextEditingController(); + + // Persian month names + final List _monthNames = [ + 'فروردین', + 'اردیبهشت', + 'خرداد', + 'تیر', + 'مرداد', + 'شهریور', + 'مهر', + 'آبان', + 'آذر', + 'دی', + 'بهمن', + 'اسفند', + ]; + + // Day names for header + final List _dayNames = ['ش', 'ی', 'د', 'س', 'چ', 'پ', 'ج']; + + @override + void initState() { + super.initState(); + _currentMonth = Jalali.now(); + _generateCalendar(); + _updateDisplayValue(); + } + + @override + void didUpdateWidget(MonthlyDataCalendar oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.selectedDate != widget.selectedDate || oldWidget.dayData != widget.dayData) { + _generateCalendar(); + _updateDisplayValue(); + } + } + + DayData? _getDayData(String formattedDate) { + return widget.dayData?[formattedDate]; + } + + // Check if a date is enabled (today or 2 days ago) + bool _isDateEnabled(Jalali date) { + final today = Jalali.now(); + final twoDaysAgo = today.addDays(-2); + final oneDayAgo = today.addDays(-1); + + final dateStr = date.formatCompactDate(); + final todayStr = today.formatCompactDate(); + final twoDaysAgoStr = twoDaysAgo.formatCompactDate(); + final oneDayAgoStr = oneDayAgo.formatCompactDate(); + + return dateStr == todayStr || dateStr == twoDaysAgoStr || dateStr == oneDayAgoStr; + } + + void _generateCalendar() { + final days = []; + final year = _currentMonth.year; + final month = _currentMonth.month; + final daysInMonth = _currentMonth.monthLength; + + // Get first day of month to determine starting position + final firstDayOfMonth = Jalali(year, month, 1); + final dayOfWeek = firstDayOfMonth.weekDay; // 1 = Saturday in shamsi_date + + // Add empty cells for days before the first day of month + for (int i = 1; i < dayOfWeek; i++) { + days.add(null); + } + + // Add all days of the month + for (int day = 1; day <= daysInMonth; day++) { + final date = Jalali(year, month, day); + final today = Jalali.now(); + final isEnabled = _isDateEnabled(date); + final formattedDate = date.formatCompactDate(); + final data = _getDayData(formattedDate); + final hasZeroValue = isEnabled && data != null && data.value == 0; + + days.add( + DayInfo( + date: date, + day: day, + formattedDate: formattedDate, + isToday: date.year == today.year && date.month == today.month && date.day == today.day, + isEnabled: isEnabled, + hasZeroValue: hasZeroValue, + ), + ); + } + + setState(() { + _calendarDays = days; + }); + } + + void _handleDayClick(DayInfo dayInfo) { + if (dayInfo.isEnabled && !dayInfo.hasZeroValue && widget.onDateSelect != null) { + widget.onDateSelect!(dayInfo); + Navigator.pop(context); + } + } + + void _handleNextMonth() { + setState(() { + if (_currentMonth.month == 12) { + _currentMonth = Jalali(_currentMonth.year + 1, 1, 1); + } else { + _currentMonth = Jalali(_currentMonth.year, _currentMonth.month + 1, 1); + } + _generateCalendar(); + }); + } + + void _handlePrevMonth() { + setState(() { + if (_currentMonth.month == 1) { + _currentMonth = Jalali(_currentMonth.year - 1, 12, 1); + } else { + _currentMonth = Jalali(_currentMonth.year, _currentMonth.month - 1, 1); + } + _generateCalendar(); + }); + } + + bool _isSelected(String formattedDate) { + return widget.selectedDate != null && widget.selectedDate == formattedDate; + } + + String _formatNumber(int? num) { + if (num == null) return ''; + final formatter = intl.NumberFormat('#,###', 'fa'); + return formatter.format(num); + } + + void _updateDisplayValue() { + if (widget.selectedDate == null || widget.selectedDate!.isEmpty) { + _textController.text = ''; + return; + } + + final dayInfo = _calendarDays.firstWhere( + (d) => d != null && d.formattedDate == widget.selectedDate, + orElse: () => null, + ); + + if (dayInfo != null) { + final persianDay = _toPersianNumber(dayInfo.day); + _textController.text = '$persianDay ${_monthNames[dayInfo.date.month - 1]}'; + } else { + _textController.text = widget.selectedDate ?? ''; + } + } + + String _toPersianNumber(int number) { + const english = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + const persian = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; + + String result = number.toString(); + for (int i = 0; i < english.length; i++) { + result = result.replaceAll(english[i], persian[i]); + } + return result; + } + + void _showCalendarDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), + child: Container( + constraints: const BoxConstraints(maxWidth: 650, maxHeight: 650), + child: Card( + elevation: 3, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildHeader(), + const SizedBox(height: 16), + _buildDayNamesHeader(), + const SizedBox(height: 8), + _buildCalendarGrid(), + ], + ), + ), + ), + ), + ); + }, + ); + } + + Widget _buildHeader() { + return Container( + padding: const EdgeInsets.only(bottom: 16), + decoration: const BoxDecoration( + border: Border(bottom: BorderSide(color: Color(0xFFF0F0F0), width: 2)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton(onPressed: _handlePrevMonth, icon: const Icon(Icons.chevron_right)), + Text( + '${_monthNames[_currentMonth.month - 1]} ${_toPersianNumber(_currentMonth.year)}', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + color: Color(0xFF333333), + ), + ), + IconButton(onPressed: _handleNextMonth, icon: const Icon(Icons.chevron_left)), + ], + ), + ); + } + + Widget _buildDayNamesHeader() { + return Row( + children: _dayNames.map((dayName) { + return Expanded( + child: Center( + child: Text( + dayName, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Color(0xFF666666), + fontSize: 14, + ), + ), + ), + ); + }).toList(), + ); + } + + Widget _buildCalendarGrid() { + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 7, + crossAxisSpacing: 4, + mainAxisSpacing: 4, + childAspectRatio: 1, + ), + itemCount: _calendarDays.length, + itemBuilder: (context, index) { + final dayInfo = _calendarDays[index]; + + if (dayInfo == null) { + return const SizedBox(); + } + + return _buildDayCell(dayInfo); + }, + ); + } + + Widget _buildDayCell(DayInfo dayInfo) { + final data = _getDayData(dayInfo.formattedDate); + final isSelectedDay = _isSelected(dayInfo.formattedDate); + + Color bgColor = Colors.white; + Color borderColor = const Color(0xFFE0E0E0); + double opacity = 1.0; + bool isClickable = true; + + if (!dayInfo.isEnabled || dayInfo.hasZeroValue) { + bgColor = const Color(0xFFF5F5F5); + borderColor = const Color(0xFFD0D0D0); + opacity = dayInfo.hasZeroValue ? 0.4 : 0.25; + isClickable = false; + } else if (isSelectedDay) { + bgColor = const Color(0xFFE3F2FD); + borderColor = const Color(0xFF1976D2); + } + + if (dayInfo.isToday && dayInfo.isEnabled && !dayInfo.hasZeroValue) { + borderColor = const Color(0xFFFF9800); + } + + return Opacity( + opacity: opacity, + child: InkWell( + onTap: isClickable ? () => _handleDayClick(dayInfo) : null, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + color: bgColor, + border: Border.all(color: borderColor, width: 2), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 4), + Text( + _toPersianNumber(dayInfo.day), + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: dayInfo.isToday ? const Color(0xFFFF9800) : const Color(0xFF333333), + ), + ), + if (data != null && data.value != null) ...[ + Text( + _formatNumber(data.value), + style: const TextStyle( + fontSize: 10, + color: Color(0xFF1976D2), + fontWeight: FontWeight.w600, + ), + ), + ], + ], + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return TextField( + controller: _textController, + readOnly: true, + onTap: _showCalendarDialog, + decoration: InputDecoration( + labelText: widget.label, + hintText: 'انتخاب تاریخ...', + prefixIcon: IconButton( + icon: const Icon(Icons.calendar_month_outlined), + onPressed: _showCalendarDialog, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: AppColor.darkGreyLight, width: 1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: AppColor.darkGreyLight, width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: AppColor.darkGreyLight, width: 1), + ), + ), + ); + } + + @override + void dispose() { + _textController.dispose(); + super.dispose(); + } +} + +// Helper classes +class DayInfo { + final Jalali date; + final int day; + final String formattedDate; + final bool isToday; + final bool isEnabled; + final bool hasZeroValue; + + DayInfo({ + required this.date, + required this.day, + required this.formattedDate, + required this.isToday, + required this.isEnabled, + required this.hasZeroValue, + }); +} + +class DayData { + final int? value; + + DayData({this.value}); + + factory DayData.fromJson(Map json) { + return DayData(value: json['value1'] as int?); + } + + Map toJson() { + return {'value1': value}; + } + + @override + String toString() { + return 'DayData{value: $value}'; + } +} diff --git a/packages/core/lib/presentation/widget/widget.dart b/packages/core/lib/presentation/widget/widget.dart index fd8f8c9..fd91299 100644 --- a/packages/core/lib/presentation/widget/widget.dart +++ b/packages/core/lib/presentation/widget/widget.dart @@ -37,6 +37,7 @@ export 'loading/loading_widget.dart'; // other export 'logo_widget.dart'; export 'marquee/r_marquee.dart'; +export 'monthly_calender.dart'; export 'overlay_dropdown_widget/multi_select_dropdown/multi_select_dropdown.dart'; export 'overlay_dropdown_widget/multi_select_dropdown/multi_select_dropdown_logic.dart'; export 'overlay_dropdown_widget/overlay_dropdown.dart'; diff --git a/packages/core/lib/utils/network/safe_call_utils.dart b/packages/core/lib/utils/network/safe_call_utils.dart index b2fd24e..11e8cd4 100644 --- a/packages/core/lib/utils/network/safe_call_utils.dart +++ b/packages/core/lib/utils/network/safe_call_utils.dart @@ -67,7 +67,7 @@ Future gSafeCall({ if (error is DioException && error.response?.statusCode == 401) { if (showError) { - (onShowErrorMessage ?? _defaultShowErrorMessage)('خطا در احراز هویت'); + (onShowErrorMessage ?? defaultShowErrorMessage)('خطا در احراز هویت'); } onError?.call(error, stackTrace); return null; @@ -76,7 +76,7 @@ Future gSafeCall({ if (retryCount > maxRetries || !_isRetryableError(error)) { if (showError) { final message = _getErrorMessage(error); - (onShowErrorMessage ?? _defaultShowErrorMessage)(message); + (onShowErrorMessage ?? defaultShowErrorMessage)(message); } onError?.call(error, stackTrace); return null; @@ -171,7 +171,7 @@ void defaultShowSuccessMessage( ); } -void _defaultShowErrorMessage(String message) { +void defaultShowErrorMessage(String message) { Get.snackbar( 'خطا', message, diff --git a/pubspec.lock b/pubspec.lock index 33b8045..34029ea 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -101,10 +101,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "4e54dbeefdc70691ba80b3bce3976af63b5425c8c07dface348dfee664a0edc1" + sha256: a9461b8e586bf018dd4afd2e13b49b08c6a844a4b226c8d1d10f3a723cdd78c3 url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.10.1" built_collection: dependency: transitive description: @@ -739,10 +739,10 @@ packages: dependency: transitive description: name: hive_ce - sha256: d678b1b2e315c18cd7ed8fd79eda25d70a1f3852d6988bfe5461cffe260c60aa + sha256: "81d39a03c4c0ba5938260a8c3547d2e71af59defecea21793d57fc3551f0d230" url: "https://pub.dev" source: hosted - version: "2.14.0" + version: "2.15.1" hive_ce_flutter: dependency: transitive description: @@ -755,10 +755,10 @@ packages: dependency: "direct dev" description: name: hive_ce_generator - sha256: "8c677690c8ead43778ddf7ed8ff17e852dd5d22d082c75182b072842c0dc5055" + sha256: b19ac263cb37529513508ba47352c41e6de72ba879952898d9c18c9c8a955921 url: "https://pub.dev" source: hosted - version: "1.9.5" + version: "1.10.0" http: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 486b73a..b4428d8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: rasadyar_app description: "A new Flutter project." publish_to: 'none' -version: 1.3.28+25 +version: 1.3.29+26 environment: sdk: ^3.9.2 @@ -38,8 +38,8 @@ dev_dependencies: sdk: flutter flutter_lints: ^6.0.0 ##code generation - build_runner: ^2.9.0 - hive_ce_generator: ^1.9.5 + build_runner: ^2.10.1 + hive_ce_generator: ^1.10.0 freezed: ^3.2.3 json_serializable: ^6.11.1 flutter_gen_runner: ^5.12.0