From 3b5d1665d67ae2a8945391d8e8dd2ace4442c340 Mon Sep 17 00:00:00 2001 From: "mr.mojtaba" Date: Mon, 7 Jul 2025 22:50:06 +0330 Subject: [PATCH] feat : sale out of province --- .../lib/presentation/pages/sale/logic.dart | 16 +- .../logic.dart | 15 - .../view.dart | 15 - .../pages/sales_in_province/view.dart | 103 ++- .../pages/sales_out_of_province/logic.dart | 322 +--------- .../pages/buyers_page.dart | 398 ------------ .../pages/sales_page.dart | 597 ------------------ .../pages/sales_out_of_province/view.dart | 43 +- .../widgets/empty_widget.dart | 10 - .../widgets/search_widget.dart | 38 -- .../sales_out_of_province_buyers/logic.dart | 199 +++++- .../sales_out_of_province_buyers/view.dart | 267 +++++++- .../logic.dart | 213 +++++++ .../view.dart | 429 +++++++++++++ .../lib/presentation/routes/pages.dart | 8 +- .../pagination_model/pagination_model.dart | 2 + 16 files changed, 1263 insertions(+), 1412 deletions(-) delete mode 100644 packages/chicken/lib/presentation/pages/sales_in_province/sales_out_of_province_sales_list/logic.dart delete mode 100644 packages/chicken/lib/presentation/pages/sales_in_province/sales_out_of_province_sales_list/view.dart delete mode 100644 packages/chicken/lib/presentation/pages/sales_out_of_province/pages/buyers_page.dart delete mode 100644 packages/chicken/lib/presentation/pages/sales_out_of_province/pages/sales_page.dart delete mode 100644 packages/chicken/lib/presentation/pages/sales_out_of_province/widgets/empty_widget.dart delete mode 100644 packages/chicken/lib/presentation/pages/sales_out_of_province/widgets/search_widget.dart create mode 100644 packages/chicken/lib/presentation/pages/sales_out_of_province_sales_list/logic.dart create mode 100644 packages/chicken/lib/presentation/pages/sales_out_of_province_sales_list/view.dart diff --git a/packages/chicken/lib/presentation/pages/sale/logic.dart b/packages/chicken/lib/presentation/pages/sale/logic.dart index 68267a6..458c1ee 100644 --- a/packages/chicken/lib/presentation/pages/sale/logic.dart +++ b/packages/chicken/lib/presentation/pages/sale/logic.dart @@ -8,12 +8,14 @@ import 'package:rasadyar_chicken/presentation/pages/root/logic.dart'; import 'package:rasadyar_core/core.dart'; class SaleLogic extends GetxController { - Rxn?> allocatedMadeModel = Rxn?>(); + Rxn?> allocatedMadeModel = + Rxn?>(); RxList rolesProductsModel = RxList(); RxList guildsModel = [].obs; - Rxn stewardFreeDashboard = Rxn(); + Rxn stewardFreeDashboard = + Rxn(); RootLogic rootLogic = Get.find(); @@ -31,7 +33,12 @@ class SaleLogic extends GetxController { safeCall( call: () async => await rootLogic.chickenRepository.getAllocatedMade( token: rootLogic.tokenService.accessToken.value!, - queryParameters: buildQueryParams(page: 1, pageSize: 20, search: 'filter', role: 'Steward'), + queryParameters: buildQueryParams( + page: 1, + pageSize: 20, + search: 'filter', + role: 'Steward', + ), ), onSuccess: (result) { if (result != null) { @@ -74,7 +81,8 @@ class SaleLogic extends GetxController { safeCall( call: () async => await rootLogic.chickenRepository.confirmAllAllocation( token: rootLogic.tokenService.accessToken.value!, - allocationTokens: allocatedMadeModel.value?.map((e) => e.key!).toList() ?? [], + allocationTokens: + allocatedMadeModel.value?.map((e) => e.key!).toList() ?? [], ), onSuccess: (result) { getAllocatedMade(); diff --git a/packages/chicken/lib/presentation/pages/sales_in_province/sales_out_of_province_sales_list/logic.dart b/packages/chicken/lib/presentation/pages/sales_in_province/sales_out_of_province_sales_list/logic.dart deleted file mode 100644 index b3526a0..0000000 --- a/packages/chicken/lib/presentation/pages/sales_in_province/sales_out_of_province_sales_list/logic.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:get/get.dart'; - -class SalesOutOfProvinceSalesListLogic extends GetxController { - @override - void onReady() { - // TODO: implement onReady - super.onReady(); - } - - @override - void onClose() { - // TODO: implement onClose - super.onClose(); - } -} diff --git a/packages/chicken/lib/presentation/pages/sales_in_province/sales_out_of_province_sales_list/view.dart b/packages/chicken/lib/presentation/pages/sales_in_province/sales_out_of_province_sales_list/view.dart deleted file mode 100644 index b34e56f..0000000 --- a/packages/chicken/lib/presentation/pages/sales_in_province/sales_out_of_province_sales_list/view.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -import 'logic.dart'; - -class SalesOutOfProvinceSalesListPage extends StatelessWidget { - const SalesOutOfProvinceSalesListPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final SalesOutOfProvinceSalesListLogic logic = Get.put(SalesOutOfProvinceSalesListLogic()); - - return Container(); - } -} diff --git a/packages/chicken/lib/presentation/pages/sales_in_province/view.dart b/packages/chicken/lib/presentation/pages/sales_in_province/view.dart index a51938f..dd3a02a 100644 --- a/packages/chicken/lib/presentation/pages/sales_in_province/view.dart +++ b/packages/chicken/lib/presentation/pages/sales_in_province/view.dart @@ -19,7 +19,10 @@ class SalesInProvincePage extends GetView { Widget build(BuildContext context) { return Scaffold( body: BasePage( - routesWidget: ObxValue((route) => buildPageRoute(route), controller.routesName), + routesWidget: ObxValue( + (route) => buildPageRoute(route), + controller.routesName, + ), onBackPressed: () => Get.back(id: 1), onSearchChanged: (data) => controller.setSearchValue(data), filteringWidget: filterBottomSheet(), @@ -32,7 +35,10 @@ class SalesInProvincePage extends GetView { resource: data.value, hasMore: data.value.data?.next != null, isPaginating: controller.isLoadingMoreAllocationsMade.value, - onRefresh: () async => await controller.getAllocatedMade(), + onRefresh: () async { + controller.currentPage.value = 1; + await controller.getAllocatedMade(); + }, onLoadMore: () async { controller.currentPage.value++; iLog(controller.currentPage.value); @@ -83,7 +89,10 @@ class SalesInProvincePage extends GetView { await controller.confirmAllAllocations(); }, message: 'تایید یکجا', - icon: Assets.vec.clipboardTaskSvg.svg(width: 40.w, height: 40.h), + icon: Assets.vec.clipboardTaskSvg.svg( + width: 40.w, + height: 40.h, + ), backgroundColor: controller.bgConfirmAllColor.value, ), ); @@ -100,7 +109,10 @@ class SalesInProvincePage extends GetView { width: Get.width, height: 39, margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration(color: AppColor.greenLight, borderRadius: BorderRadius.circular(8)), + decoration: BoxDecoration( + color: AppColor.greenLight, + borderRadius: BorderRadius.circular(8), + ), alignment: Alignment.center, child: ObxValue((data) { return Text( @@ -153,13 +165,18 @@ class SalesInProvincePage extends GetView { child: Assets.vec.hotChickenSvg.svg( width: 24, height: 24, - colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn), + colorFilter: ColorFilter.mode( + AppColor.blueNormal, + BlendMode.srcIn, + ), ), ), Text( '${item.weightOfCarcasses?.separatedByComma}kg', textAlign: TextAlign.left, - style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + style: AppFonts.yekan12.copyWith( + color: AppColor.blueNormal, + ), ), ], ), @@ -190,7 +207,10 @@ class SalesInProvincePage extends GetView { itemListExpandedWidget(AllocatedMadeModel item, int index) { return Container( padding: EdgeInsets.all(8), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), child: Column( spacing: 8, children: [ @@ -230,12 +250,16 @@ class SalesInProvincePage extends GetView { children: [ Text( item.date?.toJalali.formatter.wN ?? 'N/A', - style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + style: AppFonts.yekan14.copyWith( + color: AppColor.textColor, + ), ), Text( '${item.date?.toJalali.formatter.d} ${item.date?.toJalali.formatter.mN ?? 'N/A'}', - style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal), + style: AppFonts.yekan14.copyWith( + color: AppColor.blueNormal, + ), ), ], ), @@ -255,7 +279,8 @@ class SalesInProvincePage extends GetView { buildRow( title: 'نام و نام خانوادگی فروشنده', - value: controller.getBuyerInformation(item)?.user?.fullname ?? 'N/A', + value: + controller.getBuyerInformation(item)?.user?.fullname ?? 'N/A', ), buildRow( @@ -273,12 +298,20 @@ class SalesInProvincePage extends GetView { title: 'افت وزن(کیلوگرم)', value: item.weightLossOfCarcasses?.toInt().toString() ?? 'N/A', ), - buildRow(title: 'قیمت کل', value: '${item.totalAmount?.separatedByComma} ریال'), + buildRow( + title: 'قیمت کل', + value: '${item.totalAmount?.separatedByComma} ریال', + ), - buildRow(title: 'کداحراز', value: item.registrationCode?.toString() ?? 'ندارد'), + buildRow( + title: 'کداحراز', + value: item.registrationCode?.toString() ?? 'ندارد', + ), buildRow( title: 'وضعیت کد احراز', - value: item.systemRegistrationCode == true ? "ارسال شده" : "ارسال نشده", + value: item.systemRegistrationCode == true + ? "ارسال شده" + : "ارسال نشده", ), Row( @@ -341,7 +374,9 @@ class SalesInProvincePage extends GetView { children: [ Text( '${isEditMode ? 'ویرایش' : 'ثبت'} توزیع/ فروش', - style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal), + style: AppFonts.yekan16Bold.copyWith( + color: AppColor.blueNormal, + ), ), Visibility( visible: isEditMode == false, @@ -355,7 +390,10 @@ class SalesInProvincePage extends GetView { initText: Jalali.now().formatCompactDate(), ), const SizedBox(height: 12), - Material(type: MaterialType.transparency, child: productDropDown()), + Material( + type: MaterialType.transparency, + child: productDropDown(), + ), const SizedBox(height: 12), SizedBox( height: 40, @@ -403,7 +441,12 @@ class SalesInProvincePage extends GetView { ], validator: (value) { if (int.parse(value!.clearComma) > - (controller.rootLogic.inventoryModel.value?.totalRemainWeight?.toInt() ?? + (controller + .rootLogic + .inventoryModel + .value + ?.totalRemainWeight + ?.toInt() ?? 100)) { return 'وزن تخصیصی بیشتر از موجودی انبار است'; } @@ -424,7 +467,8 @@ class SalesInProvincePage extends GetView { ], onChanged: (p0) { - controller.pricePerKilo.value = int.tryParse(p0.clearComma) ?? 0; + controller.pricePerKilo.value = + int.tryParse(p0.clearComma) ?? 0; }, keyboardType: TextInputType.number, label: 'قیمت هر کیلو', @@ -450,7 +494,8 @@ class SalesInProvincePage extends GetView { height: 40, onPressed: data.value ? () async { - if (controller.formKey.currentState?.validate() ?? false) { + if (controller.formKey.currentState?.validate() ?? + false) { iLog("s2"); controller.setSubmitData(); iLog("s3"); @@ -527,14 +572,20 @@ class SalesInProvincePage extends GetView { child: Column( spacing: 16, children: [ - Text('فیلترها', style: AppFonts.yekan16Bold.copyWith(color: AppColor.darkGreyDarkHover)), + Text( + 'فیلترها', + style: AppFonts.yekan16Bold.copyWith( + color: AppColor.darkGreyDarkHover, + ), + ), Row( spacing: 8, children: [ Expanded( child: timeFilterWidget( date: controller.fromDateFilter, - onChanged: (jalali) => controller.fromDateFilter.value = jalali, + onChanged: (jalali) => + controller.fromDateFilter.value = jalali, ), ), Expanded( @@ -583,7 +634,10 @@ class SalesInProvincePage extends GetView { Assets.vec.calendarSvg.svg( width: 24, height: 24, - colorFilter: const ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn), + colorFilter: const ColorFilter.mode( + AppColor.blueNormal, + BlendMode.srcIn, + ), ), Text( isFrom ? 'از' : 'تا', @@ -592,9 +646,12 @@ class SalesInProvincePage extends GetView { Expanded( child: ObxValue((data) { return Text( - date.value?.formatCompactDate() ?? Jalali.now().formatCompactDate(), + date.value?.formatCompactDate() ?? + Jalali.now().formatCompactDate(), textAlign: TextAlign.center, - style: AppFonts.yekan16.copyWith(color: AppColor.lightGreyNormalActive), + style: AppFonts.yekan16.copyWith( + color: AppColor.lightGreyNormalActive, + ), ); }, date), ), diff --git a/packages/chicken/lib/presentation/pages/sales_out_of_province/logic.dart b/packages/chicken/lib/presentation/pages/sales_out_of_province/logic.dart index 91c25d8..d3e9353 100644 --- a/packages/chicken/lib/presentation/pages/sales_out_of_province/logic.dart +++ b/packages/chicken/lib/presentation/pages/sales_out_of_province/logic.dart @@ -1,318 +1,66 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:rasadyar_auth/data/utils/safe_call.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/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'; -import 'package:rasadyar_chicken/data/models/response/steward_free_sale_bar/steward_free_sale_bar.dart'; import 'package:rasadyar_chicken/presentation/pages/root/logic.dart'; import 'package:rasadyar_chicken/presentation/pages/sale/logic.dart'; +import 'package:rasadyar_chicken/presentation/pages/sales_out_of_province_buyers/logic.dart'; +import 'package:rasadyar_chicken/presentation/pages/sales_out_of_province_sales_list/logic.dart'; import 'package:rasadyar_core/core.dart'; class SalesOutOfProvinceLogic extends GetxController { RootLogic get rootLogic => Get.find(); + SaleLogic get saleLogic => Get.find(); + + SalesOutOfProvinceBuyersLogic get buyersLogic => + Get.find(); + + SalesOutOfProvinceSalesListLogic get saleListLogic => + Get.find(); + RxInt selectedSegmentIndex = 0.obs; RxBool isExpanded = false.obs; - RxBool isBuyerSubmitButtonEnabled = false.obs; - RxBool isSaleSubmitButtonEnabled = false.obs; + RxList isExpandedList = [].obs; Rx fromDateFilter = Jalali.now().obs; Rx toDateFilter = Jalali.now().obs; RxnString searchedValue = RxnString(); RxList routesName = RxList(); - //TODO add this to Di - ImagePicker imagePicker = ImagePicker(); + @override + void onInit() { + super.onInit(); + routesName.value = [...saleLogic.routesName, 'خارج استان'].toList(); - Rx>> buyerList = - Resource>.loading().obs; - Rx>> salesList = - Resource>.loading().obs; - - Rxn selectedProduct = Rxn(); - Rxn selectedBuyer = Rxn(); - - RxList cites = [].obs; - Rxn selectedProvince = Rxn(); - Rxn selectedCity = Rxn(); - - GlobalKey formKey = GlobalKey(); - TextEditingController buyerNameController = TextEditingController(); - TextEditingController buyerLastNameController = TextEditingController(); - TextEditingController buyerPhoneController = TextEditingController(); - TextEditingController buyerUnitNameController = TextEditingController(); - - //Sale - TextEditingController quarantineCodeController = TextEditingController(); - TextEditingController saleWeightController = TextEditingController(); - Rx saleDate = Jalali.now().obs; - String? key; + routesName.add(selectedSegmentIndex.value == 0 ? 'فروش' : 'خریداران'); + ever(selectedSegmentIndex, (callback) { + routesName.removeLast(); + routesName.add(callback == 0 ? 'فروش' : 'خریداران'); + }); + } @override void onReady() { super.onReady(); - getOutProvinceCarcassesBuyer(); - getOutProvinceSales(); - routesName.value = [...saleLogic.routesName, 'داخل استان'].toList(); - selectedProvince.listen((p0) => getCites()); - tLog(selectedProduct.value); - saleLogic.rolesProductsModel.listen((lists) { - selectedProduct.value = lists.first; - }); - setupListeners(); - debounce( - searchedValue, - (callback) => getOutProvinceCarcassesBuyer(), - time: Duration(milliseconds: 2000), - ); - } - @override - void onClose() { - buyerNameController.dispose(); - buyerLastNameController.dispose(); - buyerPhoneController.dispose(); - buyerUnitNameController.dispose(); - selectedCity.value = null; - selectedProvince.value = null; - isExpandedList.clear(); - - super.onClose(); - } - - Future getOutProvinceCarcassesBuyer() async { - await safeCall( - call: () => rootLogic.chickenRepository.getOutProvinceCarcassesBuyer( - token: rootLogic.tokenService.accessToken.value!, - queryParameters: buildQueryParams( - pageSize: 10, - page: 1, - state: 'buyer-list', - search: 'filter', - role: 'Steward', - value: searchedValue.value ?? '', - ), - ), - onSuccess: (res) { - if ((res?.count ?? 0) == 0) { - buyerList.value = Resource>.empty(); - } else { - buyerList.value = Resource>.success(res!.results!); - } - }, - ); - } - - Future getOutProvinceSales() async { - await safeCall( - call: () => rootLogic.chickenRepository.getStewardFreeSaleBar( - token: rootLogic.tokenService.accessToken.value!, - queryParameters: buildQueryParams( - pageSize: 10, - page: 1, - state: 'buyer-list', - search: 'filter', - role: 'Steward', - value: searchedValue.value ?? '', - fromDate: fromDateFilter.value.toDateTime(), - toDate: toDateFilter.value.toDateTime(), - ), - ), - onSuccess: (res) { - if ((res?.count ?? 0) == 0) { - salesList.value = Resource>.empty(); - } else { - salesList.value = Resource>.success(res!.results!); - } - }, - ); - } - - Future getCites() async { - await safeCall( - call: () => - rootLogic.chickenRepository.getCity(provinceName: selectedProvince.value?.name ?? ''), - onSuccess: (result) { - if (result != null && result.isNotEmpty) { - cites.value = result; - } - }, - ); - } - - void setupListeners() { - //buyer form listeners - buyerNameController.addListener(checkBuyerFormValid); - buyerLastNameController.addListener(checkBuyerFormValid); - buyerPhoneController.addListener(checkBuyerFormValid); - buyerUnitNameController.addListener(checkBuyerFormValid); - - ever(selectedProvince, (_) => checkBuyerFormValid()); - ever(selectedCity, (_) => checkBuyerFormValid()); - ever(selectedProduct, (_) => checkBuyerFormValid()); - - //sales form listeners - saleWeightController.addListener(checkSalesFormValid); - quarantineCodeController.addListener(checkSalesFormValid); - ever(selectedBuyer, (_) => checkSalesFormValid); - ever(selectedProduct, (_) => checkSalesFormValid); - ever(saleDate, (_) => checkSalesFormValid()); - } - - void checkBuyerFormValid() { - isBuyerSubmitButtonEnabled.value = - buyerNameController.text.isNotEmpty && - buyerLastNameController.text.isNotEmpty && - buyerPhoneController.text.isNotEmpty && - buyerUnitNameController.text.isNotEmpty && - selectedProvince.value != null && - selectedCity.value != null && - selectedProduct.value != null; - } - - void checkSalesFormValid() { - isSaleSubmitButtonEnabled.value = - saleDate.value.toString().isNotEmpty && - selectedProduct.value != null && - selectedBuyer.value != null && - saleWeightController.text.isNotEmpty && - quarantineCodeController.text.isNotEmpty; - } - - Future createBuyer() async { - bool res = false; - if (!(formKey.currentState?.validate() ?? false)) { - return res; - } - await safeCall( - call: () async { - OutProvinceCarcassesBuyer buyer = OutProvinceCarcassesBuyer( - province: selectedProvince.value!.name, - city: selectedCity.value!.name, - firstName: buyerNameController.text, - lastName: buyerLastNameController.text, - unitName: buyerUnitNameController.text, - mobile: buyerPhoneController.text, - role: 'Steward', - ); - final res = await rootLogic.chickenRepository.createOutProvinceCarcassesBuyer( - token: rootLogic.tokenService.accessToken.value!, - body: buyer, - ); - }, - onSuccess: (result) { - getOutProvinceCarcassesBuyer(); - resetSubmitForm(); - res = true; - }, - ); - return res; - } - - void resetSubmitForm() { - buyerNameController.clear(); - buyerLastNameController.clear(); - buyerPhoneController.clear(); - buyerUnitNameController.clear(); - selectedProvince.value = null; - selectedCity.value = null; - selectedProduct.value = null; - key = null; - isBuyerSubmitButtonEnabled.value = false; - } - - void setEditDataBuyer(OutProvinceCarcassesBuyer item) { - buyerNameController.text = item.firstName ?? ''; - buyerLastNameController.text = item.lastName ?? ''; - buyerUnitNameController.text = item.unitName ?? ''; - buyerPhoneController.text = item.mobile ?? ''; - selectedProvince.value = IranProvinceCityModel(name: item.province); - selectedCity.value = IranProvinceCityModel(name: item.city); - isBuyerSubmitButtonEnabled.value = true; - } - - void setEditDataSales(StewardFreeSaleBar item) { - quarantineCodeController.text = item.clearanceCode ?? ''; - saleWeightController.text = item.weightOfCarcasses?.toInt().toString() ?? ''; - saleDate.value = Jalali.fromDateTime(DateTime.parse(item.date!)); - selectedCity.value = IranProvinceCityModel(name: item.city); - selectedBuyer.value = buyerList.value.data?.firstWhere( - (element) => element.key == item.buyer?.key, - ); - selectedProduct.value = saleLogic.rolesProductsModel.value.first; - key = item.key; - isSaleSubmitButtonEnabled.value = true; - } - - Future deleteStewardPurchaseOutOfProvince(String key) async { - await safeCall( - call: () => rootLogic.chickenRepository.deleteStewardPurchasesOutSideOfTheProvince( - token: rootLogic.tokenService.accessToken.value!, - stewardFreeBarKey: key, - ), - ); - } - - Future createSale() async { - bool res = false; - StewardFreeSaleBarRequest requestBody = StewardFreeSaleBarRequest( - buyerKey: selectedBuyer.value?.key, - numberOfCarcasses: 0, - weightOfCarcasses: int.tryParse(saleWeightController.text.clearComma), - date: saleDate.value.toDateTime().formattedDashedGregorian, - clearanceCode: quarantineCodeController.text, - productKey: selectedProduct.value?.key, - ); - await safeCall( - call: () => rootLogic.chickenRepository.createOutProvinceStewardFreeBar( - token: rootLogic.tokenService.accessToken.value!, - body: requestBody, - ), - onSuccess: (_) { - res = true; - }, - ); - return res; - } - - void clearSaleForm() { - quarantineCodeController.clear(); - saleWeightController.clear(); - saleDate.value = Jalali.now(); - selectedBuyer.value = null; - selectedProduct.value = null; - isBuyerSubmitButtonEnabled.value = false; - } - - Future editSale() async { - bool res = false; - StewardFreeSaleBarRequest requestBody = StewardFreeSaleBarRequest( - numberOfCarcasses: 0, - weightOfCarcasses: int.tryParse(saleWeightController.text.clearComma), - date: saleDate.value.toDateTime().formattedDashedGregorian, - clearanceCode: quarantineCodeController.text, - key: key, - ); - await safeCall( - call: () => rootLogic.chickenRepository.updateOutProvinceStewardFreeBar( - token: rootLogic.tokenService.accessToken.value!, - body: requestBody, - ), - onSuccess: (_) { - res = true; - }, - ); - return res; } void setSearchValue(String value) { searchedValue.value = value.trim(); + if (selectedSegmentIndex.value == 0) { + saleListLogic.searchedValue.value = value.trim(); + } else { + buyersLogic.searchedValue.value = value.trim(); + } } void submitFilter() { - //TODO: Implement filter logic + if (selectedSegmentIndex.value == 0) { + saleListLogic.fromDateFilter.value = fromDateFilter.value; + saleListLogic.toDateFilter.value = toDateFilter.value; + saleListLogic.getOutProvinceSales(); + } else { + buyersLogic.fromDateFilter.value = fromDateFilter.value; + buyersLogic.toDateFilter.value = toDateFilter.value; + buyersLogic.getOutProvinceCarcassesBuyer(); + } } } diff --git a/packages/chicken/lib/presentation/pages/sales_out_of_province/pages/buyers_page.dart b/packages/chicken/lib/presentation/pages/sales_out_of_province/pages/buyers_page.dart deleted file mode 100644 index 409d67f..0000000 --- a/packages/chicken/lib/presentation/pages/sales_out_of_province/pages/buyers_page.dart +++ /dev/null @@ -1,398 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.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/presentation/pages/sales_out_of_province/logic.dart'; -import 'package:rasadyar_chicken/presentation/pages/sales_out_of_province/widgets/search_widget.dart'; -import 'package:rasadyar_core/core.dart'; - -import '../widgets/empty_widget.dart'; - -class BuyersPage extends GetView { - const BuyersPage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Column( - children: [ - buyerListWidget(), - ], - ), - floatingActionButton: RFab.add( - onPressed: () { - Get.bottomSheet( - addOrEditBuyerBottomSheet(), - isScrollControlled: true, - ); - }, - ), - floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, - ); - } - - Widget buyerListWidget() { - return ObxValue((data) { - switch (data.value.status) { - case Status.initial: - case Status.loading: - return Center(child: CupertinoActivityIndicator()); - case Status.success: - return ListView.separated( - shrinkWrap: true, - physics: BouncingScrollPhysics(), - padding: EdgeInsets.fromLTRB(8, 8, 18, 80), - itemBuilder: (context, index) { - return ObxValue( - (expandList) => buyerListItem( - expandList: expandList, - index: index, - item: data.value.data![index], - ), - controller.isExpandedList, - ); - }, - separatorBuilder: (context, index) => SizedBox(height: 8), - itemCount: data.value.data?.length ?? 0, - ); - case Status.error: - return Center( - child: Text( - data.value.message ?? 'خطا در دریافت اطلاعات', - style: AppFonts.yekan16.copyWith(color: AppColor.error), - ), - ); - case Status.empty: - return emptyWidget(); - } - }, controller.buyerList); - } - - GestureDetector buyerListItem({ - required RxList expandList, - required int index, - required OutProvinceCarcassesBuyer item, - }) { - return GestureDetector( - onTap: () { - if (expandList.contains(index)) { - controller.isExpandedList.remove(index); - } else { - controller.isExpandedList.add(index); - } - }, - child: AnimatedSize( - duration: Duration(milliseconds: 400), - alignment: Alignment.center, - child: Stack( - clipBehavior: Clip.none, - alignment: Alignment.centerRight, - children: [ - AnimatedSize( - duration: Duration(milliseconds: 300), - child: Container( - width: Get.width - 30, - alignment: Alignment.center, - decoration: BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular(8), - border: Border.all( - width: 0.5, - color: AppColor.darkGreyNormal, - ), - ), - child: AnimatedCrossFade( - alignment: Alignment.center, - firstChild: Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SizedBox(width: 12), - - Expanded( - flex: 2, - child: Column( - children: [ - Text( - item.buyer?.fullname ?? 'N/A', - textAlign: TextAlign.center, - style: AppFonts.yekan14.copyWith( - color: AppColor.blueNormal, - ), - ), - - SizedBox(height: 2), - Text( - item.buyer?.mobile ?? 'N/A', - textAlign: TextAlign.center, - style: AppFonts.yekan14.copyWith( - color: AppColor.bgDark, - ), - ), - ], - ), - ), - - SizedBox(width: 8), - Expanded( - flex: 2, - child: Text( - '${item.unitName}', - textAlign: TextAlign.center, - style: AppFonts.yekan12.copyWith( - color: AppColor.bgDark, - ), - ), - ), - Expanded( - flex: 1, - child: Text( - '${item.buyer?.province}\n${item.buyer?.city}', - textAlign: TextAlign.center, - style: AppFonts.yekan12.copyWith( - color: AppColor.darkGreyDark, - ), - ), - ), - - Icon(CupertinoIcons.chevron_down, size: 12), - SizedBox(width: 8), - ], - ), - ), - secondChild: Container( - padding: EdgeInsets.fromLTRB(8, 12, 14, 12), - - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - ), - child: Column( - spacing: 8, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - controller.setEditDataBuyer(item); - Get.bottomSheet( - addOrEditBuyerBottomSheet(true), - isScrollControlled: true, - ).whenComplete(() { - controller.resetSubmitForm(); - }); - }, - child: Assets.vec.editSvg.svg( - width: 20, - height: 20, - colorFilter: ColorFilter.mode( - AppColor.blueNormal, - BlendMode.srcIn, - ), - ), - ), - - Text( - '${item.province}-${item.city}', - textAlign: TextAlign.center, - style: AppFonts.yekan16.copyWith( - color: AppColor.greenDark, - ), - ), - - SizedBox(), - ], - ), - - buildRow('مشخصات خریدار', item.fullname ?? 'N/A'), - buildRow('نام واحد', item.unitName ?? 'N/A'), - buildRow( - 'تعداد درخواست ها', - '${item.requestsInfo?.numberOfRequests.separatedByComma}', - ), - buildRow( - 'وزن', - '${item.requestsInfo?.totalWeight.separatedByComma}', - ), - ], - ), - ), - crossFadeState: expandList.contains(index) - ? CrossFadeState.showSecond - : CrossFadeState.showFirst, - duration: Duration(milliseconds: 300), - ), - ), - ), - Positioned( - right: -12, - child: Container( - width: index < 999 ? 24 : null, - height: index < 999 ? 24 : null, - padding: EdgeInsets.all(2), - decoration: BoxDecoration( - color: AppColor.greenLightHover, - borderRadius: BorderRadius.circular(4), - border: Border.all( - width: 0.50, - color: AppColor.greenDarkActive, - ), - ), - alignment: Alignment.center, - child: Text( - (index + 1).toString(), - style: AppFonts.yekan12.copyWith(color: Colors.black), - ), - ), - ), - ], - ), - ), - ); - } - - Widget buildRow(String title, String value) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - flex: 2, - child: Text( - title, - textAlign: TextAlign.right, - style: AppFonts.yekan14.copyWith( - color: AppColor.darkGreyDarkHover, - ), - ), - ), - Flexible( - flex: 2, - child: Text( - value, - textAlign: TextAlign.left, - - style: AppFonts.yekan14.copyWith( - color: AppColor.darkGreyDarkHover, - ), - ), - ), - ], - ), - ); - } - - Widget addOrEditBuyerBottomSheet([bool isOnEdit = false]) { - return BaseBottomSheet( - height: 600, - child: SingleChildScrollView( - child: Form( - key: controller.formKey, - child: Column( - spacing: 16, - children: [ - Text( - isOnEdit ? 'ویرایش خریدار' : 'افزودن خریدار', - style: AppFonts.yekan16Bold.copyWith( - color: AppColor.darkGreyDarkHover, - ), - ), - - RTextField( - controller: controller.buyerPhoneController, - label: 'تلفن خریدار', - keyboardType: TextInputType.phone, - borderColor: AppColor.darkGreyLight, - maxLength: 11, - validator: (value) { - if (value == null || value.isEmpty) { - return 'لطفاً شماره موبایل را وارد کنید'; - } - // حذف کاماها برای اعتبارسنجی - String cleaned = value.replaceAll(',', ''); - if (cleaned.length != 11) { - return 'شماره موبایل باید ۱۱ رقم باشد'; - } - if (!cleaned.startsWith('09')) { - return 'شماره موبایل باید با 09 شروع شود'; - } - return null; - }, - ), - RTextField( - controller: controller.buyerNameController, - label: 'نام خریدار', - borderColor: AppColor.darkGreyLight, - ), - RTextField( - controller: controller.buyerLastNameController, - label: 'نام خانوادگی خریدار', - borderColor: AppColor.darkGreyLight, - ), - - RTextField( - controller: controller.buyerUnitNameController, - label: 'نام واحد', - borderColor: AppColor.darkGreyLight, - ), - _provinceWidget(), - _cityWidget(), - submitButtonWidget(isOnEdit), - SizedBox(), - ], - ), - ), - ), - ); - } - - Widget submitButtonWidget(bool isOnEdit) { - return ObxValue((data) { - return RElevated( - text: isOnEdit ? 'ویرایش' : 'ثبت', - onPressed: data.value - ? () async { - var res = await controller.createBuyer(); - if (res) { - Get.back(); - } - } - : null, - height: 40, - ); - }, controller.isBuyerSubmitButtonEnabled); - } - - Widget _provinceWidget() { - return Obx(() { - return OverlayDropdownWidget( - items: controller.rootLogic.provinces, - onChanged: (value) { - controller.selectedProvince.value = value; - print('Selected Product: ${value.name}'); - }, - selectedItem: controller.selectedProvince.value, - itemBuilder: (item) => Text(item.name ?? 'بدون نام'), - labelBuilder: (item) => Text(item?.name ?? 'انتخاب استان'), - ); - }); - } - - Widget _cityWidget() { - return ObxValue((data) { - return OverlayDropdownWidget( - items: data, - onChanged: (value) { - controller.selectedCity.value = value; - print('Selected Product: ${value.name}'); - }, - selectedItem: controller.selectedCity.value, - itemBuilder: (item) => Text(item.name ?? 'بدون نام'), - labelBuilder: (item) => Text(item?.name ?? 'انتخاب شهر'), - ); - }, controller.cites); - } -} diff --git a/packages/chicken/lib/presentation/pages/sales_out_of_province/pages/sales_page.dart b/packages/chicken/lib/presentation/pages/sales_out_of_province/pages/sales_page.dart deleted file mode 100644 index 7072bcb..0000000 --- a/packages/chicken/lib/presentation/pages/sales_out_of_province/pages/sales_page.dart +++ /dev/null @@ -1,597 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.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'; -import 'package:rasadyar_chicken/data/models/response/steward_free_sale_bar/steward_free_sale_bar.dart'; -import 'package:rasadyar_chicken/presentation/pages/sales_out_of_province/logic.dart'; -import 'package:rasadyar_chicken/presentation/pages/sales_out_of_province/widgets/empty_widget.dart'; -import 'package:rasadyar_core/core.dart'; - -import '../widgets/search_widget.dart'; - -class SalesPage extends GetView { - const SalesPage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Column( - children: [ - /* searchWidget(controller.searchIsSelected, (data) { - controller.searchedValue.value = data; - - //TODO: Implement search functionality - controller.getOutProvinceSales(); - }),*/ - - salesListWidget(), - ], - ), - floatingActionButton: RFab.add( - onPressed: () { - Get.bottomSheet(addOrEditSaleBottomSheet(), isScrollControlled: true); - }, - ), - floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, - ); - } - - Widget salesListWidget() { - return ObxValue((data) { - switch (data.value.status) { - case Status.initial: - case Status.loading: - return Center(child: CupertinoActivityIndicator()); - case Status.success: - - return ListView.separated( - shrinkWrap: true, - physics: BouncingScrollPhysics(), - padding: EdgeInsets.fromLTRB(8, 8, 18, 80), - itemBuilder: (context, index) { - return ObxValue( - (expandList) => salesListItem( - expandList: expandList, - index: index, - item: data.value.data![index], - ), - controller.isExpandedList, - ); - }, - separatorBuilder: (context, index) => SizedBox(height: 8), - itemCount: data.value.data?.length ?? 0, - ); - case Status.error: - return Center( - child: Text( - data.value.message ?? 'خطا در دریافت اطلاعات', - style: AppFonts.yekan16.copyWith(color: AppColor.error), - ), - ); - case Status.empty: - return emptyWidget(); - } - }, controller.salesList); - } - - GestureDetector salesListItem({ - required RxList expandList, - required int index, - required StewardFreeSaleBar item, - }) { - return GestureDetector( - onTap: () { - if (expandList.contains(index)) { - controller.isExpandedList.remove(index); - } else { - controller.isExpandedList.add(index); - } - }, - child: AnimatedSize( - duration: Duration(milliseconds: 400), - alignment: Alignment.center, - child: Stack( - clipBehavior: Clip.none, - alignment: Alignment.centerRight, - children: [ - AnimatedSize( - duration: Duration(milliseconds: 300), - child: Container( - width: Get.width - 30, - alignment: Alignment.center, - decoration: BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular(8), - border: Border.all( - width: 0.5, - color: AppColor.darkGreyNormal, - ), - ), - child: AnimatedCrossFade( - alignment: Alignment.center, - firstChild: Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SizedBox(width: 12), - Expanded( - flex: 3, - child: Text( - item.date?.formattedJalaliDate ?? 'N/A', - textAlign: TextAlign.center, - style: AppFonts.yekan12.copyWith( - color: AppColor.bgDark, - ), - ), - ), - SizedBox(width: 4), - Expanded( - flex: 5, - child: Column( - children: [ - Text( - item.buyer?.fullname ?? 'N/A', - textAlign: TextAlign.center, - style: AppFonts.yekan14.copyWith( - color: AppColor.blueNormal, - ), - ), - - SizedBox(height: 2), - Text( - item.buyer?.mobile ?? 'N/A', - textAlign: TextAlign.center, - style: AppFonts.yekan14.copyWith( - color: AppColor.bgDark, - ), - ), - ], - ), - ), - SizedBox(width: 4), - Expanded( - flex: 4, - child: Column( - spacing: 8, - children: [ - Text( - item.buyer?.unitName ?? 'N/A', - textAlign: TextAlign.center, - style: AppFonts.yekan12.copyWith( - color: AppColor.bgDark, - ), - ), - Text( - '${item.weightOfCarcasses?.separatedByComma ?? 0}KG', - textAlign: TextAlign.center, - style: AppFonts.yekan12.copyWith( - color: AppColor.bgDark, - ), - ), - ], - ), - ), - Expanded( - flex: 2, - child: Text( - '${item.buyer?.province}\n${item.buyer?.city}', - textAlign: TextAlign.center, - style: AppFonts.yekan12.copyWith( - color: AppColor.bgDark, - ), - ), - ), - SizedBox(width: 8), - Icon(CupertinoIcons.chevron_down, size: 12), - SizedBox(width: 8), - ], - ), - ), - secondChild: Container( - padding: EdgeInsets.fromLTRB(8, 12, 14, 12), - - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - ), - child: Column( - spacing: 8, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () { - controller.setEditDataSales(item); - Get.bottomSheet( - addOrEditSaleBottomSheet(true), - isScrollControlled: true, - ).whenComplete(() { - controller.resetSubmitForm(); - }); - }, - child: Assets.vec.editSvg.svg( - width: 20, - height: 20, - colorFilter: ColorFilter.mode( - AppColor.blueNormal, - BlendMode.srcIn, - ), - ), - ), - - Text( - '${item.province}-${item.city}', - textAlign: TextAlign.center, - style: AppFonts.yekan16.copyWith( - color: AppColor.greenDark, - ), - ), - - GestureDetector( - onTap: () { - buildDeleteDialog( - onConfirm: () => controller - .deleteStewardPurchaseOutOfProvince( - item.key!, - ), - ); - }, - child: Assets.vec.trashSvg.svg( - width: 20, - height: 20, - colorFilter: ColorFilter.mode( - AppColor.error, - BlendMode.srcIn, - ), - ), - ), - ], - ), - Container( - height: 32, - padding: EdgeInsets.symmetric(horizontal: 4), - decoration: ShapeDecoration( - color: AppColor.blueLight, - shape: RoundedRectangleBorder( - side: BorderSide( - width: 1, - color: AppColor.blueLightHover, - ), - borderRadius: BorderRadius.circular(8), - ), - ), - child: buildRow( - 'تاریخ', - item.date?.formattedJalaliDateYHMS ?? 'N/A', - ), - ), - buildRow( - 'مشخصات خریدار', - item.buyer?.fullname ?? 'N/A', - ), - buildRow('تلفن خریدار', item.buyer?.mobile ?? 'N/A'), - buildRow('نام واحد', item.buyer?.unitName ?? 'N/A'), - buildRow( - 'وزن لاشه', - '${item.weightOfCarcasses?.separatedByComma}', - ), - ], - ), - ), - crossFadeState: expandList.contains(index) - ? CrossFadeState.showSecond - : CrossFadeState.showFirst, - duration: Duration(milliseconds: 300), - ), - ), - ), - Positioned( - right: -12, - child: Container( - width: index < 999 ? 24 : null, - height: index < 999 ? 24 : null, - padding: EdgeInsets.all(2), - decoration: BoxDecoration( - color: AppColor.greenLightHover, - borderRadius: BorderRadius.circular(4), - border: Border.all( - width: 0.50, - color: AppColor.greenDarkActive, - ), - ), - alignment: Alignment.center, - child: Text( - (index + 1).toString(), - style: AppFonts.yekan12.copyWith(color: Colors.black), - ), - ), - ), - ], - ), - ), - ); - } - - Widget buildRow(String title, String value) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - flex: 2, - child: Text( - title, - textAlign: TextAlign.right, - style: AppFonts.yekan14.copyWith( - color: AppColor.darkGreyDarkHover, - ), - ), - ), - Flexible( - flex: 2, - child: Text( - value, - textAlign: TextAlign.left, - - style: AppFonts.yekan14.copyWith( - color: AppColor.darkGreyDarkHover, - ), - ), - ), - ], - ), - ); - } - - Widget addOrEditSaleBottomSheet([bool isOnEdit = false]) { - return BaseBottomSheet( - height: 600, - child: SingleChildScrollView( - child: Form( - key: controller.formKey, - child: Column( - spacing: 16, - children: [ - Text( - isOnEdit ? 'ویرایش فروش' : 'افزودن فروش', - style: AppFonts.yekan16Bold.copyWith( - color: AppColor.darkGreyDarkHover, - ), - ), - _productWidget(), - _buyerWidget(), - RTextField( - controller: controller.saleWeightController, - label: 'وزن لاشه', - keyboardType: TextInputType.number, - borderColor: AppColor.darkGreyLight, - - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - SeparatorInputFormatter(), - ], - - validator: (value) { - if (value == null) { - return 'لطفاً وزن لاشه را وارد کنید'; - } - return null; - }, - ), - RTextField( - controller: controller.quarantineCodeController, - label: 'کد قرنطینه', - borderColor: AppColor.darkGreyLight, - validator: (value) { - if (value == null) { - return 'لطفاً کد قرنطینه را وارد کنید'; - } - return null; - }, - ), - - Row( - spacing: 8, - children: [ - Expanded( - child: timeFilterWidget( - date: controller.saleDate, - onChanged: (jalali) => controller.saleDate.value = jalali, - ), - ), - ], - ), - - submitButtonWidget(isOnEdit), - SizedBox(), - ], - ), - ), - ), - ); - } - - Widget submitButtonWidget(bool isOnEdit) { - return ObxValue((data) { - return RElevated( - text: isOnEdit ? 'ویرایش' : 'ثبت', - onPressed: data.value - ? () async { - var res = isOnEdit - ? await controller.editSale() - : await controller.createSale(); - if (res) { - controller.getOutProvinceSales(); - controller.clearSaleForm(); - Get.back(); - } - } - : null, - height: 40, - ); - }, controller.isSaleSubmitButtonEnabled); - } - - Widget _buyerWidget() { - return Obx(() { - return OverlayDropdownWidget( - items: controller.buyerList.value.data ?? [], - onChanged: (value) { - controller.selectedBuyer.value = value; - }, - selectedItem: controller.selectedBuyer.value, - itemBuilder: (item) => Text(item.buyer?.fullname ?? 'بدون نام'), - labelBuilder: (item) => Text(item?.buyer?.fullname ?? 'انتخاب خریدار'), - ); - }); - } - - Widget _productWidget() { - return ObxValue((data) { - return OverlayDropdownWidget( - items: controller.saleLogic.rolesProductsModel, - onChanged: (value) { - controller.selectedProduct.value = value; - }, - selectedItem: controller.selectedProduct.value, - initialValue: controller.selectedProduct.value, - itemBuilder: (item) => Text(item.name ?? 'بدون نام'), - labelBuilder: (item) => Text(item?.name ?? 'انتخاب محصول'), - ); - }, controller.selectedProduct); - } - - GestureDetector timeFilterWidget({ - isFrom = true, - required Rx date, - required Function(Jalali jalali) onChanged, - }) { - return GestureDetector( - onTap: () { - Get.bottomSheet(modalDatePicker((value) => onChanged(value))); - }, - child: Container( - height: 35, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - border: Border.all(width: 1, color: AppColor.bgDark), - ), - padding: EdgeInsets.symmetric(horizontal: 11, vertical: 4), - child: Row( - spacing: 8, - children: [ - Assets.vec.calendarSvg.svg( - width: 24, - height: 24, - colorFilter: const ColorFilter.mode( - AppColor.bgDark, - BlendMode.srcIn, - ), - ), - Text( - 'تاریخ', - style: AppFonts.yekan16.copyWith(color: AppColor.bgDark), - ), - Expanded( - child: ObxValue((data) { - return Text( - date.value.formatCompactDate(), - textAlign: TextAlign.center, - style: AppFonts.yekan16.copyWith( - color: AppColor.darkGreyDark, - ), - ); - }, date), - ), - ], - ), - ), - ); - } - - Container modalDatePicker(ValueChanged onDateSelected) { - Jalali? tempPickedDate; - return Container( - height: 250, - color: Colors.white, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - child: Row( - children: [ - SizedBox(width: 20), - RElevated( - height: 35, - width: 70, - textStyle: AppFonts.yekan14.copyWith(color: Colors.white), - onPressed: () { - onDateSelected(tempPickedDate ?? Jalali.now()); - Get.back(); - }, - text: 'تایید', - ), - Spacer(), - RElevated( - height: 35, - width: 70, - backgroundColor: AppColor.error, - textStyle: AppFonts.yekan14.copyWith(color: Colors.white), - onPressed: () { - onDateSelected(tempPickedDate ?? Jalali.now()); - Get.back(); - }, - text: 'لغو', - ), - SizedBox(width: 20), - ], - ), - ), - Divider(height: 0, thickness: 1), - Expanded( - child: Container( - child: PersianCupertinoDatePicker( - initialDateTime: controller.saleDate.value, - mode: PersianCupertinoDatePickerMode.date, - onDateTimeChanged: (dateTime) { - tempPickedDate = dateTime; - }, - ), - ), - ), - ], - ), - ); - } - - Future buildDeleteDialog({ - required Future Function() onConfirm, - }) async { - await Get.defaultDialog( - title: 'حذف', - middleText: 'آیا از حذف این مورد مطمئن هستید؟', - confirm: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: AppColor.error, - foregroundColor: Colors.white, - ), - onPressed: () async { - await onConfirm(); - Get.back(); - }, - child: Text('بله'), - ), - cancel: ElevatedButton( - onPressed: () { - Get.back(); - }, - child: Text('خیر'), - ), - ).whenComplete(() => controller.getOutProvinceSales()); - } -} diff --git a/packages/chicken/lib/presentation/pages/sales_out_of_province/view.dart b/packages/chicken/lib/presentation/pages/sales_out_of_province/view.dart index 565c964..ac3f977 100644 --- a/packages/chicken/lib/presentation/pages/sales_out_of_province/view.dart +++ b/packages/chicken/lib/presentation/pages/sales_out_of_province/view.dart @@ -1,13 +1,8 @@ -import 'dart:io'; - - import 'package:flutter/material.dart'; -import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart'; -import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart'; -import 'package:rasadyar_chicken/data/models/response/steward_free_bar/steward_free_bar.dart'; -import 'package:rasadyar_chicken/presentation/pages/sales_out_of_province/pages/buyers_page.dart'; -import 'package:rasadyar_chicken/presentation/pages/sales_out_of_province/pages/sales_page.dart'; +import 'package:rasadyar_chicken/presentation/pages/sales_out_of_province_buyers/view.dart'; +import 'package:rasadyar_chicken/presentation/pages/sales_out_of_province_sales_list/view.dart'; import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_chicken/presentation/widget/page_route.dart'; import 'package:rasadyar_core/core.dart'; import 'logic.dart'; @@ -18,12 +13,22 @@ class SalesOutOfProvincePage extends GetView { @override Widget build(BuildContext context) { return BasePage( - routes: controller.routesName, + routesWidget: ObxValue( + (route) => buildPageRoute(route), + controller.routesName, + ), onBackPressed: () => Get.back(id: 1), onSearchChanged: (data) => controller.setSearchValue(data), filteringWidget: filterBottomSheet(), widgets: [ - segmentWidget() + segmentWidget(), + Expanded( + child: ObxValue((index) { + return index.value == 0 + ? SalesOutOfProvinceSalesListPage() + : SalesOutOfProvinceBuyersPage(); + }, controller.selectedSegmentIndex), + ), ], ); } @@ -35,12 +40,13 @@ class SalesOutOfProvincePage extends GetView { children: [ Expanded( child: RSegment( - children: ['در انتظار', 'همه'], - selectedIndex: 0, + children: ['فروش', 'خریداران'], + selectedIndex: controller.selectedSegmentIndex.value, borderColor: const Color(0xFFB4B4B4), selectedBorderColor: AppColor.blueNormal, selectedBackgroundColor: AppColor.blueLight, - onSegmentSelected: (index) => controller.selectedSegmentIndex.value = index, + onSegmentSelected: (index) => + controller.selectedSegmentIndex.value = index, backgroundColor: AppColor.whiteGreyNormal, ), ), @@ -55,14 +61,20 @@ class SalesOutOfProvincePage extends GetView { child: Column( spacing: 16, children: [ - Text('فیلترها', style: AppFonts.yekan16Bold.copyWith(color: AppColor.darkGreyDarkHover)), + Text( + 'فیلترها', + style: AppFonts.yekan16Bold.copyWith( + color: AppColor.darkGreyDarkHover, + ), + ), Row( spacing: 8, children: [ Expanded( child: dateFilterWidget( date: controller.fromDateFilter, - onChanged: (jalali) => controller.fromDateFilter.value = jalali, + onChanged: (jalali) => + controller.fromDateFilter.value = jalali, ), ), Expanded( @@ -88,5 +100,4 @@ class SalesOutOfProvincePage extends GetView { ), ); } - } diff --git a/packages/chicken/lib/presentation/pages/sales_out_of_province/widgets/empty_widget.dart b/packages/chicken/lib/presentation/pages/sales_out_of_province/widgets/empty_widget.dart deleted file mode 100644 index d02076d..0000000 --- a/packages/chicken/lib/presentation/pages/sales_out_of_province/widgets/empty_widget.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:rasadyar_core/core.dart'; - -Widget emptyWidget() { - return Expanded( - child: Center( - child: Text('داده ای دریافت نشد!', style: AppFonts.yekan16.copyWith(color: AppColor.darkGreyDarkHover)), - ), - ); -} diff --git a/packages/chicken/lib/presentation/pages/sales_out_of_province/widgets/search_widget.dart b/packages/chicken/lib/presentation/pages/sales_out_of_province/widgets/search_widget.dart deleted file mode 100644 index ca5518d..0000000 --- a/packages/chicken/lib/presentation/pages/sales_out_of_province/widgets/search_widget.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:rasadyar_core/core.dart'; - -ObxValue searchWidget( - RxBool searchIsSelected, - void Function(String) onChanged, -) { - return ObxValue((data) { - return AnimatedContainer( - duration: Duration(milliseconds: 300), - padding: EdgeInsets.only(top: 5), - curve: Curves.easeInOut, - height: data.value ? 50 : 0, - child: Visibility( - visible: data.value, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: RTextField( - suffixIcon: Padding( - padding: const EdgeInsets.all(12.0), - child: Assets.vec.searchSvg.svg( - width: 10, - height: 10, - colorFilter: ColorFilter.mode( - AppColor.blueNormal, - BlendMode.srcIn, - ), - ), - ), - hintText: 'جستجو', - onChanged: onChanged, - controller: TextEditingController(), - ), - ), - ), - ); - }, searchIsSelected); -} diff --git a/packages/chicken/lib/presentation/pages/sales_out_of_province_buyers/logic.dart b/packages/chicken/lib/presentation/pages/sales_out_of_province_buyers/logic.dart index 0afd06b..79c2df0 100644 --- a/packages/chicken/lib/presentation/pages/sales_out_of_province_buyers/logic.dart +++ b/packages/chicken/lib/presentation/pages/sales_out_of_province_buyers/logic.dart @@ -1,15 +1,208 @@ -import 'package:get/get.dart'; +import 'package:flutter/material.dart'; +import 'package:rasadyar_auth/data/utils/safe_call.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/presentation/pages/root/logic.dart'; +import 'package:rasadyar_chicken/presentation/pages/sale/logic.dart'; +import 'package:rasadyar_chicken/presentation/pages/sales_out_of_province/logic.dart'; +import 'package:rasadyar_chicken/presentation/utils/utils.dart'; +import 'package:rasadyar_core/core.dart'; class SalesOutOfProvinceBuyersLogic extends GetxController { + RootLogic get rootLogic => Get.find(); + + SaleLogic get saleLogic => Get.find(); + + SalesOutOfProvinceLogic get saleOutOfProvince => + Get.find(); + + RxInt currentPage = 1.obs; + RxList isExpandedList = [].obs; + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + RxnString searchedValue = RxnString(); + + RxBool isLoadingMoreAllocationsMade = false.obs; + RxBool isBuyerSubmitButtonEnabled = false.obs; + + RxList cites = [].obs; + Rxn selectedProvince = Rxn(); + Rxn selectedCity = Rxn(); + + GlobalKey formKey = GlobalKey(); + TextEditingController buyerNameController = TextEditingController(); + TextEditingController buyerLastNameController = TextEditingController(); + TextEditingController buyerPhoneController = TextEditingController(); + TextEditingController buyerUnitNameController = TextEditingController(); + String? key; + + Rx>> buyerList = + Resource>.loading().obs; + + @override + void onInit() { + super.onInit(); + getOutProvinceCarcassesBuyer(); + } + @override void onReady() { - // TODO: implement onReady super.onReady(); + + selectedProvince.listen((p0) => getCites()); + + debounce( + searchedValue, + (callback) => getOutProvinceCarcassesBuyer(), + time: Duration(milliseconds: timeDebounce), + ); } @override void onClose() { - // TODO: implement onClose + buyerNameController.dispose(); + buyerLastNameController.dispose(); + buyerPhoneController.dispose(); + buyerUnitNameController.dispose(); + selectedCity.value = null; + selectedProvince.value = null; + isExpandedList.clear(); super.onClose(); } + + Future getOutProvinceCarcassesBuyer([ + bool isLoadingMore = false, + ]) async { + if (isLoadingMore) { + isLoadingMoreAllocationsMade.value = true; + } else { + buyerList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; // Reset to first page if search value is set + } + + await safeCall( + call: () => rootLogic.chickenRepository.getOutProvinceCarcassesBuyer( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + pageSize: 20, + page: currentPage.value, + state: 'buyer-list', + search: 'filter', + role: 'Steward', + value: searchedValue.value ?? '', + ), + ), + onError: (error, stackTrace) => + isLoadingMoreAllocationsMade.value = false, + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + buyerList.value = + Resource>.empty(); + } else { + buyerList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(buyerList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + + isLoadingMoreAllocationsMade.value = false; + } + }, + ); + } + + void resetSubmitForm() { + buyerNameController.clear(); + buyerLastNameController.clear(); + buyerPhoneController.clear(); + buyerUnitNameController.clear(); + selectedProvince.value = null; + selectedCity.value = null; + } + + Future getCites() async { + await safeCall( + call: () => rootLogic.chickenRepository.getCity( + provinceName: selectedProvince.value?.name ?? '', + ), + onSuccess: (result) { + if (result != null && result.isNotEmpty) { + cites.value = result; + } + }, + ); + } + + void setupListeners() { + buyerNameController.addListener(checkBuyerFormValid); + buyerLastNameController.addListener(checkBuyerFormValid); + buyerPhoneController.addListener(checkBuyerFormValid); + buyerUnitNameController.addListener(checkBuyerFormValid); + ever(selectedProvince, (_) => checkBuyerFormValid()); + ever(selectedCity, (_) => checkBuyerFormValid()); + } + + void checkBuyerFormValid() { + isBuyerSubmitButtonEnabled.value = + buyerNameController.text.isNotEmpty && + buyerLastNameController.text.isNotEmpty && + buyerPhoneController.text.isNotEmpty && + buyerUnitNameController.text.isNotEmpty && + selectedProvince.value != null && + selectedCity.value != null; + } + + Future createBuyer() async { + bool res = false; + if (!(formKey.currentState?.validate() ?? false)) { + return res; + } + await safeCall( + call: () async { + OutProvinceCarcassesBuyer buyer = OutProvinceCarcassesBuyer( + province: selectedProvince.value!.name, + city: selectedCity.value!.name, + firstName: buyerNameController.text, + lastName: buyerLastNameController.text, + unitName: buyerUnitNameController.text, + mobile: buyerPhoneController.text, + role: 'Steward', + ); + final res = await rootLogic.chickenRepository + .createOutProvinceCarcassesBuyer( + token: rootLogic.tokenService.accessToken.value!, + body: buyer, + ); + }, + onSuccess: (result) { + getOutProvinceCarcassesBuyer(); + resetSubmitForm(); + res = true; + }, + ); + return res; + } + + void setEditDataBuyer(OutProvinceCarcassesBuyer item) { + buyerNameController.text = item.firstName ?? ''; + buyerLastNameController.text = item.lastName ?? ''; + buyerUnitNameController.text = item.unitName ?? ''; + buyerPhoneController.text = item.mobile ?? ''; + selectedProvince.value = IranProvinceCityModel(name: item.province); + selectedCity.value = IranProvinceCityModel(name: item.city); + isBuyerSubmitButtonEnabled.value = true; + } } diff --git a/packages/chicken/lib/presentation/pages/sales_out_of_province_buyers/view.dart b/packages/chicken/lib/presentation/pages/sales_out_of_province_buyers/view.dart index 74e9a24..1ff77c3 100644 --- a/packages/chicken/lib/presentation/pages/sales_out_of_province_buyers/view.dart +++ b/packages/chicken/lib/presentation/pages/sales_out_of_province_buyers/view.dart @@ -1,15 +1,272 @@ import 'package:flutter/material.dart'; -import 'package:get/get.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/presentation/widget/list_item/list_item.dart'; +import 'package:rasadyar_chicken/presentation/widget/list_row_item.dart'; +import 'package:rasadyar_core/core.dart'; import 'logic.dart'; -class SalesOutOfProvinceBuyersPage extends StatelessWidget { - const SalesOutOfProvinceBuyersPage({Key? key}) : super(key: key); +class SalesOutOfProvinceBuyersPage + extends GetView { + const SalesOutOfProvinceBuyersPage({super.key}); @override Widget build(BuildContext context) { - final SalesOutOfProvinceBuyersLogic logic = Get.put(SalesOutOfProvinceBuyersLogic()); + return Scaffold( + body: ObxValue((data) { + return RPaginatedListView( + onLoadMore: () async => controller.getOutProvinceCarcassesBuyer(true), + onRefresh: () async { + controller.currentPage.value = 1; + await controller.getOutProvinceCarcassesBuyer(); + }, + listType: ListType.separated, + resource: data.value, + padding: EdgeInsets.fromLTRB(8, 8, 8, 80), + itemBuilder: (context, index) { + var item = data.value.data!.results![index]; + return ObxValue((val) { + return ListItem2( + selected: val.contains(index), + onTap: () => controller.isExpandedList.toggle(index), + index: index, + child: itemListWidget(item), + secondChild: itemListExpandedWidget(item), + labelColor: AppColor.blueLight, + labelIcon: Assets.vec.timerSvg.path, + ); + }, controller.isExpandedList); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + ); + }, controller.buyerList), + floatingActionButton: RFab.add( + onPressed: () { + Get.bottomSheet( + addOrEditBuyerBottomSheet(), + isScrollControlled: true, + ); + }, + ), + floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, + ); + } - return Container(); + Widget addOrEditBuyerBottomSheet([bool isOnEdit = false]) { + return BaseBottomSheet( + height: 600, + child: SingleChildScrollView( + child: Form( + key: controller.formKey, + child: Column( + spacing: 16, + children: [ + Text( + isOnEdit ? 'ویرایش خریدار' : 'افزودن خریدار', + style: AppFonts.yekan16Bold.copyWith( + color: AppColor.darkGreyDarkHover, + ), + ), + + RTextField( + controller: controller.buyerPhoneController, + label: 'تلفن خریدار', + keyboardType: TextInputType.phone, + borderColor: AppColor.darkGreyLight, + maxLength: 11, + validator: (value) { + if (value == null || value.isEmpty) { + return 'لطفاً شماره موبایل را وارد کنید'; + } + // حذف کاماها برای اعتبارسنجی + String cleaned = value.replaceAll(',', ''); + if (cleaned.length != 11) { + return 'شماره موبایل باید ۱۱ رقم باشد'; + } + if (!cleaned.startsWith('09')) { + return 'شماره موبایل باید با 09 شروع شود'; + } + return null; + }, + ), + RTextField( + controller: controller.buyerNameController, + label: 'نام خریدار', + borderColor: AppColor.darkGreyLight, + ), + RTextField( + controller: controller.buyerLastNameController, + label: 'نام خانوادگی خریدار', + borderColor: AppColor.darkGreyLight, + ), + + RTextField( + controller: controller.buyerUnitNameController, + label: 'نام واحد', + borderColor: AppColor.darkGreyLight, + ), + _provinceWidget(), + _cityWidget(), + submitButtonWidget(isOnEdit), + SizedBox(), + ], + ), + ), + ), + ); + } + + Widget submitButtonWidget(bool isOnEdit) { + return ObxValue((data) { + return RElevated( + text: isOnEdit ? 'ویرایش' : 'ثبت', + onPressed: data.value + ? () async { + var res = await controller.createBuyer(); + if (res) { + Get.back(); + } + } + : null, + height: 40, + ); + }, controller.isBuyerSubmitButtonEnabled); + } + + Widget _provinceWidget() { + return Obx(() { + return OverlayDropdownWidget( + items: controller.rootLogic.provinces, + onChanged: (value) { + controller.selectedProvince.value = value; + print('Selected Product: ${value.name}'); + }, + selectedItem: controller.selectedProvince.value, + itemBuilder: (item) => Text(item.name ?? 'بدون نام'), + labelBuilder: (item) => Text(item?.name ?? 'انتخاب استان'), + ); + }); + } + + Widget _cityWidget() { + return ObxValue((data) { + return OverlayDropdownWidget( + items: data, + onChanged: (value) { + controller.selectedCity.value = value; + print('Selected Product: ${value.name}'); + }, + selectedItem: controller.selectedCity.value, + itemBuilder: (item) => Text(item.name ?? 'بدون نام'), + labelBuilder: (item) => Text(item?.name ?? 'انتخاب شهر'), + ); + }, controller.cites); + } + + itemListWidget(OutProvinceCarcassesBuyer item) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox(width: 12), + + Expanded( + flex: 2, + child: Column( + children: [ + Text( + item.buyer?.fullname ?? 'N/A', + textAlign: TextAlign.center, + style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal), + ), + + SizedBox(height: 2), + Text( + item.buyer?.mobile ?? 'N/A', + textAlign: TextAlign.center, + style: AppFonts.yekan14.copyWith(color: AppColor.bgDark), + ), + ], + ), + ), + + Expanded( + flex: 2, + child: Text( + '${item.unitName}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.bgDark), + ), + ), + Expanded( + flex: 2, + child: Text( + '${item.buyer?.province}\n${item.buyer?.city}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.darkGreyDark), + ), + ), + ], + ); + } + + itemListExpandedWidget(OutProvinceCarcassesBuyer item) { + return Container( + padding: EdgeInsets.fromLTRB(8, 12, 14, 12), + + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + spacing: 8, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () { + controller.setEditDataBuyer(item); + Get.bottomSheet( + addOrEditBuyerBottomSheet(true), + isScrollControlled: true, + ).whenComplete(() { + controller.resetSubmitForm(); + }); + }, + child: Assets.vec.editSvg.svg( + width: 20, + height: 20, + colorFilter: ColorFilter.mode( + AppColor.blueNormal, + BlendMode.srcIn, + ), + ), + ), + + Text( + '${item.province}-${item.city}', + textAlign: TextAlign.center, + style: AppFonts.yekan16.copyWith(color: AppColor.greenDark), + ), + + SizedBox(), + ], + ), + + buildRow(title: 'مشخصات خریدار', value: item.fullname ?? 'N/A'), + buildRow(title: 'نام واحد', value: item.unitName ?? 'N/A'), + buildRow( + title: 'تعداد درخواست ها', + value: '${item.requestsInfo?.numberOfRequests.separatedByComma}', + ), + buildRow( + title: 'وزن', + value: '${item.requestsInfo?.totalWeight.separatedByComma}', + ), + ], + ), + ); } } diff --git a/packages/chicken/lib/presentation/pages/sales_out_of_province_sales_list/logic.dart b/packages/chicken/lib/presentation/pages/sales_out_of_province_sales_list/logic.dart new file mode 100644 index 0000000..b3d2716 --- /dev/null +++ b/packages/chicken/lib/presentation/pages/sales_out_of_province_sales_list/logic.dart @@ -0,0 +1,213 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_auth/data/utils/safe_call.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/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'; +import 'package:rasadyar_chicken/data/models/response/steward_free_sale_bar/steward_free_sale_bar.dart'; +import 'package:rasadyar_chicken/presentation/pages/root/logic.dart'; +import 'package:rasadyar_chicken/presentation/pages/sale/logic.dart'; +import 'package:rasadyar_chicken/presentation/pages/sales_out_of_province_buyers/logic.dart'; +import 'package:rasadyar_chicken/presentation/utils/utils.dart'; +import 'package:rasadyar_core/core.dart'; + +class SalesOutOfProvinceSalesListLogic extends GetxController { + RootLogic get rootLogic => Get.find(); + + SaleLogic get saleLogic => Get.find(); + + SalesOutOfProvinceBuyersLogic get buyerLogic => + Get.find(); + + RxInt selectedSegmentIndex = 0.obs; + RxBool isExpanded = false.obs; + RxInt currentPage = 1.obs; + RxBool isSaleSubmitButtonEnabled = false.obs; + RxList isExpandedList = [].obs; + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + RxnString searchedValue = RxnString(); + RxList routesName = RxList(); + RxBool isLoadingMoreAllocationsMade = false.obs; + Rxn selectedCity = Rxn(); + + //TODO add this to Di + ImagePicker imagePicker = ImagePicker(); + + GlobalKey formKey = GlobalKey(); + TextEditingController quarantineCodeController = TextEditingController(); + TextEditingController saleWeightController = TextEditingController(); + Rx saleDate = Jalali.now().obs; + String? key; + + Rx>> salesList = + Resource>.loading().obs; + + Rxn selectedProduct = Rxn(); + Rxn selectedBuyer = Rxn(); + + @override + void onInit() { + super.onInit(); + getOutProvinceSales(); + } + + @override + void onReady() { + super.onReady(); + + debounce( + searchedValue, + (callback) => getOutProvinceSales(), + time: Duration(milliseconds: timeDebounce), + ); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + Future getOutProvinceSales([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreAllocationsMade.value = true; + } else { + salesList.value = Resource>.loading(); + } + await safeCall( + call: () => rootLogic.chickenRepository.getStewardFreeSaleBar( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + pageSize: 20, + page: currentPage.value, + state: 'buyer-list', + search: 'filter', + role: 'Steward', + value: searchedValue.value ?? '', + fromDate: fromDateFilter.value.toDateTime(), + toDate: toDateFilter.value.toDateTime(), + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + salesList.value = + Resource>.empty(); + } else { + salesList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(salesList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + + isLoadingMoreAllocationsMade.value = false; + } + }, + ); + } + + void setupListeners() { + saleWeightController.addListener(checkSalesFormValid); + quarantineCodeController.addListener(checkSalesFormValid); + ever(selectedBuyer, (_) => checkSalesFormValid); + ever(selectedProduct, (_) => checkSalesFormValid); + ever(saleDate, (_) => checkSalesFormValid()); + } + + void checkSalesFormValid() { + isSaleSubmitButtonEnabled.value = + saleDate.value.toString().isNotEmpty && + selectedProduct.value != null && + selectedBuyer.value != null && + saleWeightController.text.isNotEmpty && + quarantineCodeController.text.isNotEmpty; + } + + void setEditDataSales(StewardFreeSaleBar item) { + quarantineCodeController.text = item.clearanceCode ?? ''; + saleWeightController.text = + item.weightOfCarcasses?.toInt().toString() ?? ''; + saleDate.value = Jalali.fromDateTime(DateTime.parse(item.date!)); + selectedCity.value = IranProvinceCityModel(name: item.city); + selectedBuyer.value = buyerLogic.buyerList.value.data?.results?.firstWhere( + (element) => element.key == item.buyer?.key, + ); + selectedProduct.value = saleLogic.rolesProductsModel.first; + key = item.key; + isSaleSubmitButtonEnabled.value = true; + } + + Future deleteStewardPurchaseOutOfProvince(String key) async { + await safeCall( + call: () => rootLogic.chickenRepository + .deleteStewardPurchasesOutSideOfTheProvince( + token: rootLogic.tokenService.accessToken.value!, + stewardFreeBarKey: key, + ), + ); + } + + Future createSale() async { + bool res = false; + StewardFreeSaleBarRequest requestBody = StewardFreeSaleBarRequest( + buyerKey: selectedBuyer.value?.key, + numberOfCarcasses: 0, + weightOfCarcasses: int.tryParse(saleWeightController.text.clearComma), + date: saleDate.value.toDateTime().formattedDashedGregorian, + clearanceCode: quarantineCodeController.text, + productKey: selectedProduct.value?.key, + ); + await safeCall( + call: () => rootLogic.chickenRepository.createOutProvinceStewardFreeBar( + token: rootLogic.tokenService.accessToken.value!, + body: requestBody, + ), + onSuccess: (_) { + res = true; + }, + ); + return res; + } + + void clearSaleForm() { + quarantineCodeController.clear(); + saleWeightController.clear(); + saleDate.value = Jalali.now(); + selectedBuyer.value = null; + selectedProduct.value = null; + } + + Future editSale() async { + bool res = false; + StewardFreeSaleBarRequest requestBody = StewardFreeSaleBarRequest( + numberOfCarcasses: 0, + weightOfCarcasses: int.tryParse(saleWeightController.text.clearComma), + date: saleDate.value.toDateTime().formattedDashedGregorian, + clearanceCode: quarantineCodeController.text, + key: key, + ); + await safeCall( + call: () => rootLogic.chickenRepository.updateOutProvinceStewardFreeBar( + token: rootLogic.tokenService.accessToken.value!, + body: requestBody, + ), + onSuccess: (_) { + res = true; + }, + ); + return res; + } + + void resetSubmitForm() { + selectedCity.value = null; + selectedProduct.value = null; + key = null; + } +} diff --git a/packages/chicken/lib/presentation/pages/sales_out_of_province_sales_list/view.dart b/packages/chicken/lib/presentation/pages/sales_out_of_province_sales_list/view.dart new file mode 100644 index 0000000..14bfa20 --- /dev/null +++ b/packages/chicken/lib/presentation/pages/sales_out_of_province_sales_list/view.dart @@ -0,0 +1,429 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.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'; +import 'package:rasadyar_chicken/data/models/response/steward_free_sale_bar/steward_free_sale_bar.dart'; +import 'package:rasadyar_chicken/presentation/widget/list_item/list_item.dart'; +import 'package:rasadyar_chicken/presentation/widget/list_row_item.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class SalesOutOfProvinceSalesListPage + extends GetView { + const SalesOutOfProvinceSalesListPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ObxValue((data) { + return RPaginatedListView( + onLoadMore: () async => controller.getOutProvinceSales(true), + onRefresh: () async { + controller.currentPage.value = 1; + await controller.getOutProvinceSales(); + }, + listType: ListType.separated, + resource: data.value, + padding: EdgeInsets.fromLTRB(8, 8, 8, 80), + itemBuilder: (context, index) { + var item = data.value.data!.results![index]; + return ObxValue((val) { + return ListItem2( + selected: val.contains(index), + onTap: () => controller.isExpandedList.toggle(index), + index: index, + child: itemListWidget(item), + secondChild: itemListExpandedWidget(item), + labelColor: AppColor.blueLight, + labelIcon: Assets.vec.timerSvg.path, + ); + }, controller.isExpandedList); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + ); + }, controller.salesList), + floatingActionButton: RFab.add( + onPressed: () { + Get.bottomSheet(addOrEditSaleBottomSheet(), isScrollControlled: true); + }, + ), + floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, + ); + } + + itemListWidget(StewardFreeSaleBar item) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox(width: 12), + Expanded( + flex: 3, + child: Text( + item.date?.formattedJalaliDate ?? 'N/A', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.bgDark), + ), + ), + SizedBox(width: 4), + Expanded( + flex: 5, + child: Column( + children: [ + Text( + item.buyer?.fullname ?? 'N/A', + textAlign: TextAlign.center, + style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal), + ), + + SizedBox(height: 2), + Text( + item.buyer?.mobile ?? 'N/A', + textAlign: TextAlign.center, + style: AppFonts.yekan14.copyWith(color: AppColor.bgDark), + ), + ], + ), + ), + SizedBox(width: 4), + Expanded( + flex: 4, + child: Column( + spacing: 8, + children: [ + Text( + item.buyer?.unitName ?? 'N/A', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.bgDark), + ), + Text( + '${item.weightOfCarcasses?.separatedByComma ?? 0}KG', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.bgDark), + ), + ], + ), + ), + Expanded( + flex: 2, + child: Text( + '${item.buyer?.province}\n${item.buyer?.city}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.bgDark), + ), + ), + ], + ); + } + + itemListExpandedWidget(StewardFreeSaleBar item) { + return Container( + padding: EdgeInsets.fromLTRB(8, 12, 14, 12), + + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + spacing: 8, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () { + controller.setEditDataSales(item); + Get.bottomSheet( + addOrEditSaleBottomSheet(true), + isScrollControlled: true, + ).whenComplete(() { + controller.resetSubmitForm(); + }); + }, + child: Assets.vec.editSvg.svg( + width: 20, + height: 20, + colorFilter: ColorFilter.mode( + AppColor.blueNormal, + BlendMode.srcIn, + ), + ), + ), + + Text( + '${item.province}-${item.city}', + textAlign: TextAlign.center, + style: AppFonts.yekan16.copyWith(color: AppColor.greenDark), + ), + + GestureDetector( + onTap: () { + buildDeleteDialog( + onConfirm: () => controller + .deleteStewardPurchaseOutOfProvince(item.key!), + onRefresh: () => controller.getOutProvinceSales(), + ); + }, + child: Assets.vec.trashSvg.svg( + width: 20, + height: 20, + colorFilter: ColorFilter.mode( + AppColor.error, + BlendMode.srcIn, + ), + ), + ), + ], + ), + Container( + height: 32, + padding: EdgeInsets.symmetric(horizontal: 4), + decoration: ShapeDecoration( + color: AppColor.blueLight, + shape: RoundedRectangleBorder( + side: BorderSide(width: 1, color: AppColor.blueLightHover), + borderRadius: BorderRadius.circular(8), + ), + ), + child: buildRow( + title: 'تاریخ', + value: item.date?.formattedJalaliDateYHMS ?? 'N/A', + ), + ), + buildRow( + title: 'مشخصات خریدار', + value: item.buyer?.fullname ?? 'N/A', + ), + buildRow(title: 'تلفن خریدار', value: item.buyer?.mobile ?? 'N/A'), + buildRow(title: 'نام واحد', value: item.buyer?.unitName ?? 'N/A'), + buildRow( + title: 'وزن لاشه', + value: '${item.weightOfCarcasses?.separatedByComma}', + ), + ], + ), + ); + } + + Widget addOrEditSaleBottomSheet([bool isOnEdit = false]) { + return BaseBottomSheet( + height: 600, + child: SingleChildScrollView( + child: Form( + key: controller.formKey, + child: Column( + spacing: 16, + children: [ + Text( + isOnEdit ? 'ویرایش فروش' : 'افزودن فروش', + style: AppFonts.yekan16Bold.copyWith( + color: AppColor.darkGreyDarkHover, + ), + ), + _productWidget(), + _buyerWidget(), + RTextField( + controller: controller.saleWeightController, + label: 'وزن لاشه', + keyboardType: TextInputType.number, + borderColor: AppColor.darkGreyLight, + + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + SeparatorInputFormatter(), + ], + + validator: (value) { + if (value == null) { + return 'لطفاً وزن لاشه را وارد کنید'; + } + return null; + }, + ), + RTextField( + controller: controller.quarantineCodeController, + label: 'کد قرنطینه', + borderColor: AppColor.darkGreyLight, + validator: (value) { + if (value == null) { + return 'لطفاً کد قرنطینه را وارد کنید'; + } + return null; + }, + ), + + Row( + spacing: 8, + children: [ + Expanded( + child: timeFilterWidget( + date: controller.saleDate, + onChanged: (jalali) => controller.saleDate.value = jalali, + ), + ), + ], + ), + + submitButtonWidget(isOnEdit), + SizedBox(), + ], + ), + ), + ), + ); + } + + Widget submitButtonWidget(bool isOnEdit) { + return ObxValue((data) { + return RElevated( + text: isOnEdit ? 'ویرایش' : 'ثبت', + onPressed: data.value + ? () async { + var res = isOnEdit + ? await controller.editSale() + : await controller.createSale(); + if (res) { + controller.getOutProvinceSales(); + controller.clearSaleForm(); + Get.back(); + } + } + : null, + height: 40, + ); + }, controller.isSaleSubmitButtonEnabled); + } + + Widget _buyerWidget() { + return Obx(() { + return OverlayDropdownWidget( + items: controller.buyerLogic.buyerList.value.data?.results ?? [], + onChanged: (value) { + controller.selectedBuyer.value = value; + }, + selectedItem: controller.selectedBuyer.value, + itemBuilder: (item) => Text(item.buyer?.fullname ?? 'بدون نام'), + labelBuilder: (item) => Text(item?.buyer?.fullname ?? 'انتخاب خریدار'), + ); + }); + } + + Widget _productWidget() { + return ObxValue((data) { + return OverlayDropdownWidget( + items: controller.saleLogic.rolesProductsModel, + onChanged: (value) { + controller.selectedProduct.value = value; + }, + selectedItem: controller.selectedProduct.value, + initialValue: controller.selectedProduct.value, + itemBuilder: (item) => Text(item.name ?? 'بدون نام'), + labelBuilder: (item) => Text(item?.name ?? 'انتخاب محصول'), + ); + }, controller.selectedProduct); + } + + GestureDetector timeFilterWidget({ + isFrom = true, + required Rx date, + required Function(Jalali jalali) onChanged, + }) { + return GestureDetector( + onTap: () { + Get.bottomSheet(modalDatePicker((value) => onChanged(value))); + }, + child: Container( + height: 35, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all(width: 1, color: AppColor.bgDark), + ), + padding: EdgeInsets.symmetric(horizontal: 11, vertical: 4), + child: Row( + spacing: 8, + children: [ + Assets.vec.calendarSvg.svg( + width: 24, + height: 24, + colorFilter: const ColorFilter.mode( + AppColor.bgDark, + BlendMode.srcIn, + ), + ), + Text( + 'تاریخ', + style: AppFonts.yekan16.copyWith(color: AppColor.bgDark), + ), + Expanded( + child: ObxValue((data) { + return Text( + date.value.formatCompactDate(), + textAlign: TextAlign.center, + style: AppFonts.yekan16.copyWith( + color: AppColor.darkGreyDark, + ), + ); + }, date), + ), + ], + ), + ), + ); + } + + Container modalDatePicker(ValueChanged onDateSelected) { + Jalali? tempPickedDate; + return Container( + height: 250, + color: Colors.white, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + child: Row( + children: [ + SizedBox(width: 20), + RElevated( + height: 35, + width: 70, + textStyle: AppFonts.yekan14.copyWith(color: Colors.white), + onPressed: () { + onDateSelected(tempPickedDate ?? Jalali.now()); + Get.back(); + }, + text: 'تایید', + ), + Spacer(), + RElevated( + height: 35, + width: 70, + backgroundColor: AppColor.error, + textStyle: AppFonts.yekan14.copyWith(color: Colors.white), + onPressed: () { + onDateSelected(tempPickedDate ?? Jalali.now()); + Get.back(); + }, + text: 'لغو', + ), + SizedBox(width: 20), + ], + ), + ), + Divider(height: 0, thickness: 1), + Expanded( + child: Container( + child: PersianCupertinoDatePicker( + initialDateTime: controller.saleDate.value, + mode: PersianCupertinoDatePickerMode.date, + onDateTimeChanged: (dateTime) { + tempPickedDate = dateTime; + }, + ), + ), + ), + ], + ), + ); + } +} diff --git a/packages/chicken/lib/presentation/routes/pages.dart b/packages/chicken/lib/presentation/routes/pages.dart index 0e384b0..b31b7c6 100644 --- a/packages/chicken/lib/presentation/routes/pages.dart +++ b/packages/chicken/lib/presentation/routes/pages.dart @@ -22,6 +22,9 @@ import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart'; import 'package:rasadyar_chicken/presentation/widget/search/logic.dart'; import 'package:rasadyar_core/core.dart'; +import '../pages/sales_out_of_province_buyers/logic.dart'; +import '../pages/sales_out_of_province_sales_list/logic.dart'; + sealed class ChickenPages { ChickenPages._(); @@ -80,10 +83,13 @@ sealed class ChickenPages { page: () => SalesOutOfProvincePage(), middlewares: [AuthMiddleware()], binding: BindingsBuilder(() { - Get.lazyPut(() => SalesOutOfProvinceLogic()); Get.lazyPut(() => RootLogic()); Get.lazyPut(() => BaseLogic()); Get.lazyPut(() => SearchLogic()); + Get.lazyPut(() => SalesOutOfProvinceLogic()); + Get.lazyPut(() => SalesOutOfProvinceBuyersLogic()); + Get.lazyPut(() => SalesOutOfProvinceSalesListLogic()); + }), ), GetPage( diff --git a/packages/core/lib/data/model/pagination_model/pagination_model.dart b/packages/core/lib/data/model/pagination_model/pagination_model.dart index 61edb74..1f8cb2b 100644 --- a/packages/core/lib/data/model/pagination_model/pagination_model.dart +++ b/packages/core/lib/data/model/pagination_model/pagination_model.dart @@ -1,4 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:rasadyar_chicken/data/models/response/out_province_carcasses_buyer/out_province_carcasses_buyer.dart'; part 'pagination_model.freezed.dart'; part 'pagination_model.g.dart'; @@ -16,4 +17,5 @@ abstract class PaginationModel with _$PaginationModel { Map json, T Function(Object?) fromJsonT, ) => _$PaginationModelFromJson(json, fromJsonT); + } \ No newline at end of file