feat : buyer in sale out of the province

This commit is contained in:
2025-06-23 16:49:12 +03:30
parent 1e3614ed58
commit 078fe9a022
13 changed files with 2268 additions and 198 deletions

View File

@@ -1,15 +1,200 @@
import 'package:get/get.dart';
import 'package:flutter/cupertino.dart';
import 'package:rasadyar_auth/data/utils/safe_call.dart';
import 'package:rasadyar_chicken/data/models/request/create_steward_free_bar/create_steward_free_bar.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/out_of_province/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class BuysOutOfProvinceLogic extends GetxController {
RxBool isExpanded = false.obs;
RxBool isSubmitButtonEnabled = false.obs;
RxList<int> isExpandedList = <int>[].obs;
RxBool searchIsSelected = false.obs;
//TODO add this to Di
ImagePicker imagePicker = ImagePicker();
Rx<Resource<List<StewardFreeBar>>> purchaseOutOfProvinceList = Resource<List<StewardFreeBar>>.loading().obs;
Rxn<ProductModel> selectedProduct = Rxn();
RxList<IranProvinceCityModel> cites = <IranProvinceCityModel>[].obs;
Rxn<IranProvinceCityModel> selectedProvince = Rxn();
Rxn<IranProvinceCityModel> selectedCity = Rxn();
Rxn<XFile> selectedImage = Rxn<XFile>();
RxnString _base64Image = RxnString();
RxnString editImageUrl = RxnString();
RootLogic get rootLogic => Get.find<RootLogic>();
OutOfProvinceLogic get outOfTheProvinceLogic => Get.find<OutOfProvinceLogic>();
GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController sellerNameController = TextEditingController();
TextEditingController sellerPhoneController = TextEditingController();
TextEditingController carcassWeightController = TextEditingController();
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
RxnString searchedValue = RxnString();
@override
void onReady() {
// TODO: implement onReady
super.onReady();
getStewardPurchaseOutOfProvince();
selectedProvince.listen((p0) => getCites());
tLog(selectedProduct.value);
outOfTheProvinceLogic.rolesProductsModel.listen((lists) {
selectedProduct.value = lists.first;
},);
tLog(selectedProduct.value);
setupListeners();
debounce(searchedValue, (callback) => getStewardPurchaseOutOfProvince(), time: Duration(milliseconds: 2000));
ever(searchIsSelected, (data) {
if (data == false) {
searchedValue.value = null;
}
});
}
@override
void onClose() {
// TODO: implement onClose
sellerNameController.dispose();
sellerPhoneController.dispose();
carcassWeightController.dispose();
isExpandedList.clear();
super.onClose();
}
Future<void> getStewardPurchaseOutOfProvince() async {
await safeCall(
call: () => rootLogic.chickenRepository.getStewardPurchasesOutSideOfTheProvince(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
pageSize: 10,
page: 1,
search: 'filter',
role: 'Steward',
value: searchedValue.value,
fromDate: fromDateFilter.value.toDateTime(),
toDate: toDateFilter.value.toDateTime(),
),
),
onSuccess: (res) {
if ((res?.count ?? 0) == 0) {
purchaseOutOfProvinceList.value = Resource<List<StewardFreeBar>>.empty();
} else {
purchaseOutOfProvinceList.value = Resource<List<StewardFreeBar>>.success(res!.results!);
}
},
);
}
Future<void> getCites() async {
await safeCall(
call: () => rootLogic.chickenRepository.getCity(provinceName: selectedProvince.value?.name ?? ''),
onSuccess: (result) {
if (result != null && result.isNotEmpty) {
cites.value = result;
}
},
);
}
void setupListeners() {
sellerNameController.addListener(checkFormValid);
sellerPhoneController.addListener(checkFormValid);
carcassWeightController.addListener(checkFormValid);
ever(selectedProvince, (_) => checkFormValid());
ever(selectedCity, (_) => checkFormValid());
ever(selectedProduct, (_) => checkFormValid());
ever(selectedImage, (data) async {
checkFormValid();
if (data?.path != null) {
_base64Image.value = await convertImageToBase64(data!.path);
}
});
}
void checkFormValid() {
isSubmitButtonEnabled.value =
sellerNameController.text.isNotEmpty &&
sellerPhoneController.text.isNotEmpty &&
carcassWeightController.text.isNotEmpty &&
selectedProvince.value != null &&
selectedCity.value != null &&
selectedProduct.value != null &&
selectedImage.value != null;
}
Future<bool> createStewardPurchaseOutOfProvince() async {
bool res = false;
if (!(formKey.currentState?.validate() ?? false)) {
return res;
}
await safeCall(
call: () async {
CreateStewardFreeBar createStewardFreeBar = CreateStewardFreeBar(
productKey: selectedProduct.value!.key,
killHouseName: sellerNameController.text,
killHouseMobile: sellerPhoneController.text,
province: selectedProvince.value!.name,
city: selectedCity.value!.name,
weightOfCarcasses: int.parse(carcassWeightController.text.clearComma),
barImage: _base64Image.value,
date: DateTime.now().formattedYHMS,
);
final res = await rootLogic.chickenRepository.createStewardPurchasesOutSideOfTheProvince(
token: rootLogic.tokenService.accessToken.value!,
body: createStewardFreeBar,
);
},
onSuccess: (result) {
getStewardPurchaseOutOfProvince();
resetSubmitForm();
res = true;
},
);
return res;
}
void resetSubmitForm() {
sellerNameController.clear();
sellerPhoneController.clear();
carcassWeightController.clear();
selectedProvince.value = null;
selectedCity.value = null;
selectedProduct.value = null;
selectedImage.value = null;
_base64Image.value = null;
editImageUrl.value = null;
isSubmitButtonEnabled.value = false;
}
void setEditData(StewardFreeBar item) {
editImageUrl.value = item.barImage;
sellerNameController.text = item.killHouseName ?? '';
sellerPhoneController.text = item.killHouseMobile ?? '';
carcassWeightController.text = item.weightOfCarcasses?.toInt().toString() ?? '';
selectedProvince.value = IranProvinceCityModel(name: item.province);
selectedCity.value = IranProvinceCityModel(name: item.city);
selectedProduct.value = outOfTheProvinceLogic.rolesProductsModel.firstWhere(
(element) => element.key == item.product!.key,
);
isSubmitButtonEnabled.value = true;
}
Future<void> deleteStewardPurchaseOutOfProvince(String key) async {
await safeCall(
call: () => rootLogic.chickenRepository.deleteStewardPurchasesOutSideOfTheProvince(
token: rootLogic.tokenService.accessToken.value!,
stewardFreeBarKey: key,
),
);
}
}

View File

@@ -1,13 +1,799 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.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/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/steward_free_bar/steward_free_bar.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class BuysOutOfProvincePage extends GetView<BuysOutOfProvinceLogic> {
const BuysOutOfProvincePage({super.key});
@override
Widget build(BuildContext context) {
return Container();
return Scaffold(
appBar: RAppBar(
titleTextStyle: AppFonts.yekan16Bold.copyWith(color: Colors.white),
centerTitle: true,
hasBack: true,
onBackPressed: () {
Get.back(id: 1);
},
leadingWidth: 155,
leading: Row(
mainAxisSize: MainAxisSize.min,
spacing: 6,
children: [
Assets.vec.chickenSvg.svg(
width: 24,
height: 24,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Text('رصدطیور', style: AppFonts.yekan16Bold.copyWith(color: Colors.white)),
],
),
additionalActions: [
GestureDetector(
onTap: () {
controller.searchIsSelected.value = !controller.searchIsSelected.value;
},
child: Assets.vec.searchSvg.svg(
width: 24,
height: 24,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
),
SizedBox(width: 8),
GestureDetector(
onTap: () {
Get.bottomSheet(filterBottomSheet());
},
child: Assets.vec.filterOutlineSvg.svg(
width: 20,
height: 20,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
),
SizedBox(width: 8),
],
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
routePageWidget(),
_buildSearchWidget(),
Expanded(child: saleListWidget()),
],
),
floatingActionButton: RFab.add(
onPressed: () {
Get.bottomSheet(addPurchasedInformationBottomSheet(), isScrollControlled: true);
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
);
}
Widget saleListWidget() {
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) => saleListItem(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.purchaseOutOfProvinceList);
}
Widget emptyWidget() {
return Center(
child: Text('داده ای دریافت نشد!', style: AppFonts.yekan16.copyWith(color: AppColor.darkGreyDarkHover)),
);
}
GestureDetector saleListItem({required RxList<int> expandList, required int index, required StewardFreeBar 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: Text(
item.date?.formattedJalaliDate ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
),
Expanded(
flex: 3,
child: Text(
item.killHouseName ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
),
SizedBox(width: 8,),
Expanded(
flex: 2,
child: Column(
children: [
Text(
'${item.weightOfCarcasses?.separatedByComma}kg',
textAlign: TextAlign.left,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
SizedBox(height: 2,),
Visibility(
visible: item.product?.name?.contains('مرغ گرم') ?? false,
child: Assets.vec.hotChickenSvg.svg(width: 24,height: 24, colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn))),
],
),
),
Expanded(
flex: 2,
child: Text(
'${item.province}\n${item.city}',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
),
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.setEditData(item);
Get.bottomSheet(
addPurchasedInformationBottomSheet(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.killHouseName} - ${item.killHouseMobile ?? 'N/A'}'),
buildRow('محصول', item.product?.name ??'N/A'),
buildRow('وزن خریداری شده', '${item.weightOfCarcasses?.separatedByComma} کیلوگرم'),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
Get.bottomSheet(
BaseBottomSheet(
height: 400,
child: Column(
spacing: 16,
children: [
Text(
'بارنامه',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.darkGreyDarkHover),
),
Image.network(
item.barImage ?? '',
fit: BoxFit.cover,
height: 300,
errorBuilder: (context, error, stackTrace) {
eLog(error.toString());
return Center(child: Text('خطایی پیش آمده!'));
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
print('Loading progress: $loadingProgress');
return CupertinoActivityIndicator();
},
),
],
),
),
);
},
child: Text('مشاهده بارنامه', style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal)),
),
Icon(CupertinoIcons.chevron_up, size: 12),
],
),
],
),
),
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)),
),
),
],
),
),
);
}
Row routePageWidget() {
return Row(
children: [
SizedBox(width: 8),
RichText(
text: TextSpan(
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
children: [
WidgetSpan(
child: Row(
children: [
Assets.vec.cubeSearchSvg.svg(
width: 24,
height: 24,
colorFilter: const ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
SizedBox(width: 6),
],
),
),
TextSpan(text: 'خارج استان'),
TextSpan(text: '/'),
TextSpan(text: 'خرید'),
],
),
),
],
);
}
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 addPurchasedInformationBottomSheet([bool isOnEdit = false]) {
return BaseBottomSheet(
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 16,
children: [
Text(
isOnEdit ? 'ویرایش اطلاعات خرید' : 'ثبت اطلاعات خرید',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.darkGreyDarkHover),
),
_productTypeWidget(),
RTextField(
controller: controller.sellerNameController,
label: 'نام فروشنده',
borderColor: AppColor.darkGreyLight,
),
RTextField(
controller: controller.sellerPhoneController,
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;
},
),
_provinceWidget(),
_cityWidget(),
RTextField(
controller: controller.carcassWeightController,
label: 'وزن لاشه',
keyboardType: TextInputType.number,
borderColor: AppColor.darkGreyLight,
inputFormatters: [FilteringTextInputFormatter.digitsOnly, SeparatorInputFormatter()],
),
_imageCarcasesWidget(isOnEdit),
submitButtonWidget(isOnEdit),
SizedBox(),
],
),
),
),
);
}
Widget submitButtonWidget(bool isOnEdit) {
return ObxValue((data) {
return RElevated(
text: isOnEdit ? 'ویرایش' : 'ثبت خرید',
onPressed: data.value
? () async {
var res = await controller.createStewardPurchaseOutOfProvince();
if (res) {
Get.back();
}
}
: null,
height: 40,
);
}, controller.isSubmitButtonEnabled);
}
Widget _productTypeWidget() {
tLog(controller.outOfTheProvinceLogic.rolesProductsModel);
iLog(controller.selectedProduct.value);
return Obx(() {
return OverlayDropdownWidget<ProductModel>(
items: controller.outOfTheProvinceLogic.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 ?? 'انتخاب محصول'),
);
});
}
Widget _provinceWidget() {
return Obx(() {
return OverlayDropdownWidget<IranProvinceCityModel>(
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<IranProvinceCityModel>(
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);
}
SizedBox _imageCarcasesWidget(bool isOnEdit) {
return SizedBox(
width: Get.width,
height: 250,
child: Card(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Expanded(
child: ObxValue((data) {
return Container(
width: Get.width,
decoration: BoxDecoration(color: AppColor.lightGreyNormal, borderRadius: BorderRadius.circular(8)),
child: Center(
child: isOnEdit
? Image.network(controller.editImageUrl.value ?? '')
: data.value == null
? Assets.images.placeHolder.image(height: 150, width: 200)
: Image.file(File(data.value!.path), fit: BoxFit.cover),
),
);
}, controller.selectedImage),
),
SizedBox(height: 15),
Container(
width: Get.width,
height: 40,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('تصویر بار', style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal)),
Spacer(),
GestureDetector(
onTap: () async {
controller.selectedImage.value = await controller.imagePicker.pickImage(
source: ImageSource.camera,
imageQuality: 60,
maxWidth: 1080,
maxHeight: 720,
);
},
child: Container(
decoration: ShapeDecoration(
color: AppColor.blueNormal,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
padding: EdgeInsetsGeometry.symmetric(horizontal: 6, vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('دوربین', style: AppFonts.yekan14.copyWith(color: Colors.white)),
SizedBox(width: 8),
Icon(CupertinoIcons.camera, color: Colors.white),
],
),
),
),
SizedBox(width: 16),
GestureDetector(
onTap: () async {
controller.selectedImage.value = await controller.imagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 60,
maxWidth: 1080,
maxHeight: 720,
);
},
child: Container(
decoration: ShapeDecoration(
color: AppColor.blueNormal,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
padding: EdgeInsetsGeometry.symmetric(horizontal: 6, vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('گالری', style: AppFonts.yekan14.copyWith(color: Colors.white)),
SizedBox(width: 8),
Icon(CupertinoIcons.photo, color: Colors.white),
],
),
),
),
],
),
),
],
),
),
),
);
}
Future<void> buildDeleteDialog({required Future<void> 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.getStewardPurchaseOutOfProvince());
}
Widget filterBottomSheet() {
return BaseBottomSheet(
height: 250,
child: Column(
spacing: 16,
children: [
Text('فیلترها', style: AppFonts.yekan16Bold.copyWith(color: AppColor.darkGreyDarkHover)),
Row(
spacing: 8,
children: [
Expanded(
child: timeFilterWidget(
date: controller.fromDateFilter,
onChanged: (jalali) => controller.fromDateFilter.value = jalali,
),
),
Expanded(
child: timeFilterWidget(
isFrom: false,
date: controller.toDateFilter,
onChanged: (jalali) => controller.toDateFilter.value = jalali,
),
),
],
),
SizedBox(height: 2),
RElevated(
text: 'اعمال فیلتر',
onPressed: () {
controller.getStewardPurchaseOutOfProvince();
Get.back();
},
height: 40,
),
SizedBox(height: 16),
],
),
);
}
GestureDetector timeFilterWidget({
isFrom = true,
required Rx<Jalali> 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.blueNormal),
),
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.blueNormal, BlendMode.srcIn),
),
Text(isFrom ? 'از' : 'تا', style: AppFonts.yekan16.copyWith(color: AppColor.blueNormal)),
Expanded(
child: ObxValue((data) {
return Text(
date.value.formatCompactDate(),
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.lightGreyNormalActive),
);
}, date),
),
],
),
),
);
}
Container modalDatePicker(ValueChanged<Jalali> onDateSelected) {
Jalali? tempPickedDate;
return Container(
height: 250,
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
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: Jalali.now(),
mode: PersianCupertinoDatePickerMode.date,
onDateTimeChanged: (dateTime) {
tempPickedDate = dateTime;
},
),
),
),
],
),
);
}
ObxValue<RxBool> _buildSearchWidget() {
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: (value) {
controller.searchedValue.value = value;
},
controller: TextEditingController(),
),
),
),
);
}, controller.searchIsSelected);
}
}

View File

@@ -41,20 +41,23 @@ class OutOfProvincePage extends GetView<OutOfProvinceLogic> {
children: [
Expanded(
child: _typeOuterInfoCard(
title: 'خرید خارج استان',
title: 'خرید',
iconPath: Assets.vec.cubeBottomRotationSvg.path,
foregroundColor: AppColor.blueNormal,
onTap: () {
Get.toNamed(ChickenRoutes.salesOutOfProvince,id:1);
Get.toNamed(ChickenRoutes.buysOutOfProvince, id: 1);
},
),
),
Expanded(
child: _typeOuterInfoCard(
title: 'فروش خارج استان',
title: 'فروش',
iconPath: Assets.vec.cubeTopRotationSvg.path,
foregroundColor: AppColor.greenDark,
onTap: () {},
onTap: () {
iLog('فروش');
Get.toNamed(ChickenRoutes.salesOutOfProvince, id: 1);
},
),
),
],

View File

@@ -1,14 +1,16 @@
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/create_steward_free_bar/create_steward_free_bar.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_bar/steward_free_bar.dart';
import 'package:rasadyar_chicken/presentation/pages/out_of_province/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class SalesOutOfProvinceLogic extends GetxController {
RxInt currentIndex = 0.obs;
RxBool isExpanded = false.obs;
RxBool isSubmitButtonEnabled = false.obs;
RxList<int> isExpandedList = <int>[].obs;
@@ -17,24 +19,23 @@ class SalesOutOfProvinceLogic extends GetxController {
//TODO add this to Di
ImagePicker imagePicker = ImagePicker();
Rx<Resource<List<StewardFreeBar>>> purchaseOutOfProvinceList = Resource<List<StewardFreeBar>>.loading().obs;
Rx<Resource<List<OutProvinceCarcassesBuyer>>> buyerList = Resource<List<OutProvinceCarcassesBuyer>>.loading().obs;
Rxn<ProductModel> selectedProduct = Rxn();
RxList<IranProvinceCityModel> cites = <IranProvinceCityModel>[].obs;
Rxn<IranProvinceCityModel> selectedProvince = Rxn();
Rxn<IranProvinceCityModel> selectedCity = Rxn();
Rxn<XFile> selectedImage = Rxn<XFile>();
RxnString _base64Image = RxnString();
RxnString editImageUrl = RxnString();
RootLogic get rootLogic => Get.find<RootLogic>();
OutOfProvinceLogic get outOfTheProvinceLogic => Get.find<OutOfProvinceLogic>();
GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController sellerNameController = TextEditingController();
TextEditingController sellerPhoneController = TextEditingController();
TextEditingController carcassWeightController = TextEditingController();
TextEditingController buyerNameController = TextEditingController();
TextEditingController buyerLastNameController = TextEditingController();
TextEditingController buyerPhoneController = TextEditingController();
TextEditingController buyerUnitNameController = TextEditingController();
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
@@ -43,15 +44,14 @@ class SalesOutOfProvinceLogic extends GetxController {
@override
void onReady() {
super.onReady();
getStewardPurchaseOutOfProvince();
getOutProvinceCarcassesBuyer();
selectedProvince.listen((p0) => getCites());
tLog(selectedProduct.value);
outOfTheProvinceLogic.rolesProductsModel.listen((lists) {
selectedProduct.value = lists.first;
},);
tLog(selectedProduct.value);
outOfTheProvinceLogic.rolesProductsModel.listen((lists) {
selectedProduct.value = lists.first;
});
setupListeners();
debounce(searchedValue, (callback) => getStewardPurchaseOutOfProvince(), time: Duration(milliseconds: 2000));
debounce(searchedValue, (callback) => getOutProvinceCarcassesBuyer(), time: Duration(milliseconds: 2000));
ever(searchIsSelected, (data) {
if (data == false) {
searchedValue.value = null;
@@ -61,33 +61,35 @@ class SalesOutOfProvinceLogic extends GetxController {
@override
void onClose() {
sellerNameController.dispose();
sellerPhoneController.dispose();
carcassWeightController.dispose();
buyerNameController.dispose();
buyerLastNameController.dispose();
buyerPhoneController.dispose();
buyerUnitNameController.dispose();
selectedCity.value = null;
selectedProvince.value = null;
isExpandedList.clear();
super.onClose();
}
Future<void> getStewardPurchaseOutOfProvince() async {
Future<void> getOutProvinceCarcassesBuyer() async {
await safeCall(
call: () => rootLogic.chickenRepository.getStewardPurchasesOutSideOfTheProvince(
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,
fromDate: fromDateFilter.value.toDateTime(),
toDate: toDateFilter.value.toDateTime(),
value: searchedValue.value ?? '',
),
),
onSuccess: (res) {
if ((res?.count ?? 0) == 0) {
purchaseOutOfProvinceList.value = Resource<List<StewardFreeBar>>.empty();
buyerList.value = Resource<List<OutProvinceCarcassesBuyer>>.empty();
} else {
purchaseOutOfProvinceList.value = Resource<List<StewardFreeBar>>.success(res!.results!);
buyerList.value = Resource<List<OutProvinceCarcassesBuyer>>.success(res!.results!);
}
},
);
@@ -105,56 +107,50 @@ class SalesOutOfProvinceLogic extends GetxController {
}
void setupListeners() {
sellerNameController.addListener(checkFormValid);
sellerPhoneController.addListener(checkFormValid);
carcassWeightController.addListener(checkFormValid);
buyerNameController.addListener(checkFormValid);
buyerLastNameController.addListener(checkFormValid);
buyerPhoneController.addListener(checkFormValid);
buyerUnitNameController.addListener(checkFormValid);
ever(selectedProvince, (_) => checkFormValid());
ever(selectedCity, (_) => checkFormValid());
ever(selectedProduct, (_) => checkFormValid());
ever(selectedImage, (data) async {
checkFormValid();
if (data?.path != null) {
_base64Image.value = await convertImageToBase64(data!.path);
}
});
}
void checkFormValid() {
isSubmitButtonEnabled.value =
sellerNameController.text.isNotEmpty &&
sellerPhoneController.text.isNotEmpty &&
carcassWeightController.text.isNotEmpty &&
buyerNameController.text.isNotEmpty &&
buyerLastNameController.text.isNotEmpty &&
buyerPhoneController.text.isNotEmpty &&
buyerUnitNameController.text.isNotEmpty &&
selectedProvince.value != null &&
selectedCity.value != null &&
selectedProduct.value != null &&
selectedImage.value != null;
selectedProduct.value != null;
}
Future<bool> createStewardPurchaseOutOfProvince() async {
Future<bool> createBuyer() async {
bool res = false;
if (!(formKey.currentState?.validate() ?? false)) {
return res;
}
await safeCall(
call: () async {
CreateStewardFreeBar createStewardFreeBar = CreateStewardFreeBar(
productKey: selectedProduct.value!.key,
killHouseName: sellerNameController.text,
killHouseMobile: sellerPhoneController.text,
OutProvinceCarcassesBuyer buyer = OutProvinceCarcassesBuyer(
province: selectedProvince.value!.name,
city: selectedCity.value!.name,
weightOfCarcasses: int.parse(carcassWeightController.text.clearComma),
barImage: _base64Image.value,
date: DateTime.now().formattedYHMS,
firstName: buyerNameController.text,
lastName: buyerLastNameController.text,
unitName: buyerUnitNameController.text,
mobile: buyerPhoneController.text,
role: 'Steward',
);
final res = await rootLogic.chickenRepository.createStewardPurchasesOutSideOfTheProvince(
final res = await rootLogic.chickenRepository.createOutProvinceCarcassesBuyer(
token: rootLogic.tokenService.accessToken.value!,
body: createStewardFreeBar,
body: buyer,
);
},
onSuccess: (result) {
getStewardPurchaseOutOfProvince();
getOutProvinceCarcassesBuyer();
resetSubmitForm();
res = true;
},
@@ -163,29 +159,24 @@ class SalesOutOfProvinceLogic extends GetxController {
}
void resetSubmitForm() {
sellerNameController.clear();
sellerPhoneController.clear();
carcassWeightController.clear();
buyerNameController.clear();
buyerLastNameController.clear();
buyerPhoneController.clear();
buyerUnitNameController.clear();
selectedProvince.value = null;
selectedCity.value = null;
selectedProduct.value = null;
selectedImage.value = null;
_base64Image.value = null;
editImageUrl.value = null;
isSubmitButtonEnabled.value = false;
}
void setEditData(StewardFreeBar item) {
editImageUrl.value = item.barImage;
sellerNameController.text = item.killHouseName ?? '';
sellerPhoneController.text = item.killHouseMobile ?? '';
carcassWeightController.text = item.weightOfCarcasses?.toInt().toString() ?? '';
void setEditData(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);
selectedProduct.value = outOfTheProvinceLogic.rolesProductsModel.firstWhere(
(element) => element.key == item.product!.key,
);
isSubmitButtonEnabled.value = true;
}

View File

@@ -1,11 +1,11 @@
import 'dart:io';
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/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/widgets/buyers_page.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
@@ -15,70 +15,106 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: RAppBar(
titleTextStyle: AppFonts.yekan16Bold.copyWith(color: Colors.white),
centerTitle: true,
hasBack: true,
onBackPressed: () {
Get.back(id: 1);
return ObxValue((index) {
return DefaultTabController(
length: 2,
initialIndex: index.value,
child: Scaffold(
appBar: RAppBar(
titleTextStyle: AppFonts.yekan16Bold.copyWith(color: Colors.white),
centerTitle: true,
hasBack: true,
onBackPressed: () {
Get.back(id: 1);
},
leadingWidth: 155,
leading: Row(
mainAxisSize: MainAxisSize.min,
spacing: 6,
children: [
Assets.vec.chickenSvg.svg(
width: 24,
height: 24,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Text('رصدطیور', style: AppFonts.yekan16Bold.copyWith(color: Colors.white)),
],
),
additionalActions: [
GestureDetector(
onTap: () {
controller.searchIsSelected.value = !controller.searchIsSelected.value;
},
child: Assets.vec.searchSvg.svg(
width: 24,
height: 24,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
),
SizedBox(width: 8),
GestureDetector(
onTap: () {
// Get.bottomSheet(filterBottomSheet());
},
child: Assets.vec.filterOutlineSvg.svg(
width: 20,
height: 20,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
),
SizedBox(width: 8),
],
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
routePageWidget(),
Container(
child: TabBar(
tabs: [
Tab(text: 'فروش' ),
Tab(text: 'خریداران'),
],
onTap: (value) {
controller.currentIndex.value= value;
},
labelColor: AppColor.blueNormal,
unselectedLabelColor: AppColor.mediumGreyDarkHover,
indicatorColor: AppColor.blueNormal,
indicatorSize: TabBarIndicatorSize.tab,
indicator: BoxDecoration(
color: AppColor.blueLight,
border: Border(
bottom: BorderSide(
color:AppColor.blueNormal,
),
),
),
),
),
Expanded(
child: TabBarView(
children: [
salePage(),
BuyersPage()
],
),
),
],
),
},
leadingWidth: 155,
leading: Row(
mainAxisSize: MainAxisSize.min,
spacing: 6,
children: [
Assets.vec.chickenSvg.svg(
width: 24,
height: 24,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
Text('رصدطیور', style: AppFonts.yekan16Bold.copyWith(color: Colors.white)),
],
),
additionalActions: [
GestureDetector(
onTap: () {
controller.searchIsSelected.value = !controller.searchIsSelected.value;
},
child: Assets.vec.searchSvg.svg(
width: 24,
height: 24,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
),
SizedBox(width: 8),
GestureDetector(
onTap: () {
Get.bottomSheet(filterBottomSheet());
},
child: Assets.vec.filterOutlineSvg.svg(
width: 20,
height: 20,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
),
SizedBox(width: 8),
],
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
routePageWidget(),
_buildSearchWidget(),
Expanded(child: saleListWidget()),
],
),
floatingActionButton: RFab.add(
onPressed: () {
Get.bottomSheet(addPurchasedInformationBottomSheet(), isScrollControlled: true);
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
);
);
}, controller.currentIndex);
}
Widget salePage(){
return SizedBox();
}
/*
Widget saleListWidget() {
return ObxValue((data) {
switch (data.value.status) {
@@ -92,7 +128,7 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
padding: EdgeInsets.fromLTRB(8, 8, 18, 80),
itemBuilder: (context, index) {
return ObxValue(
(expandList) => saleListItem(expandList: expandList, index: index, item: data.value.data![index]),
(expandList) => saleListItem(expandList: expandList, index: index, item: data.value.data![index]),
controller.isExpandedList,
);
},
@@ -111,14 +147,11 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
}
}, controller.purchaseOutOfProvinceList);
}
*/
Widget emptyWidget() {
return Center(
child: Text('داده ای دریافت نشد!', style: AppFonts.yekan16.copyWith(color: AppColor.darkGreyDarkHover)),
);
}
GestureDetector saleListItem({required RxList<int> expandList, required int index, required StewardFreeBar item}) {
/* GestureDetector saleListItem({required RxList<int> expandList, required int index, required StewardFreeBar item}) {
return GestureDetector(
onTap: () {
if (expandList.contains(index)) {
@@ -182,7 +215,9 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
SizedBox(height: 2,),
Visibility(
visible: item.product?.name?.contains('مرغ گرم') ?? false,
child: Assets.vec.hotChickenSvg.svg(width: 24,height: 24, colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn))),
child: Assets.vec.hotChickenSvg.svg(width: 24,
height: 24,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn))),
],
),
@@ -235,9 +270,9 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
GestureDetector(
onTap: () {
buildDeleteDialog(
onConfirm: () => controller.deleteStewardPurchaseOutOfProvince(item.key!),
);
// buildDeleteDialog(
// onConfirm: () => controller.deleteStewardPurchaseOutOfProvince(item.key!),
// );
},
child: Assets.vec.trashSvg.svg(
width: 20,
@@ -261,7 +296,8 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
),
buildRow('مشخصات فروشنده', '${item.killHouseName} - ${item.killHouseMobile ?? 'N/A'}'),
buildRow('وزن خریداری شده', '${item.weightOfCarcasses?.toInt()} کیلوگرم'),
buildRow('محصول', item.product?.name ?? 'N/A'),
buildRow('وزن خریداری شده', '${item.weightOfCarcasses?.separatedByComma} کیلوگرم'),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -297,7 +333,8 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
),
);
},
child: Text('مشاهده بارنامه', style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal)),
child: Text(
'مشاهده بارنامه', style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal)),
),
Icon(CupertinoIcons.chevron_up, size: 12),
],
@@ -329,7 +366,7 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
),
),
);
}
}*/
Row routePageWidget() {
return Row(
@@ -354,7 +391,7 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
TextSpan(text: 'خارج استان'),
TextSpan(text: '/'),
TextSpan(text: 'خرید'),
TextSpan(text: 'فروش'),
],
),
),
@@ -389,37 +426,8 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
),
);
}
Widget buildRow2(String title, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
textAlign: TextAlign.right,
style: AppFonts.yekan14.copyWith(color: AppColor.darkGreyDarkHover),
),
SizedBox(width: 8,),
Expanded(
child: Container
(
color: Colors.green,
child: Text(
value,
textAlign: TextAlign.left,
style: AppFonts.yekan14.copyWith(color: AppColor.darkGreyDarkHover),
),
),
),
],
),
);
}
Widget addPurchasedInformationBottomSheet([bool isOnEdit = false]) {
/* Widget addPurchasedInformationBottomSheet([bool isOnEdit = false]) {
return BaseBottomSheet(
child: SingleChildScrollView(
child: Form(
@@ -475,24 +483,24 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
),
),
);
}
}*/
Widget submitButtonWidget(bool isOnEdit) {
/* Widget submitButtonWidget(bool isOnEdit) {
return ObxValue((data) {
return RElevated(
text: isOnEdit ? 'ویرایش' : 'ثبت خرید',
onPressed: data.value
? () async {
var res = await controller.createStewardPurchaseOutOfProvince();
if (res) {
Get.back();
}
}
var res = await controller.createStewardPurchaseOutOfProvince();
if (res) {
Get.back();
}
}
: null,
height: 40,
);
}, controller.isSubmitButtonEnabled);
}
}*/
Widget _productTypeWidget() {
tLog(controller.outOfTheProvinceLogic.rolesProductsModel);
@@ -541,7 +549,7 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
}, controller.cites);
}
SizedBox _imageCarcasesWidget(bool isOnEdit) {
/* SizedBox _imageCarcasesWidget(bool isOnEdit) {
return SizedBox(
width: Get.width,
height: 250,
@@ -634,9 +642,9 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
),
),
);
}
}*/
Future<void> buildDeleteDialog({required Future<void> Function() onConfirm}) async {
/* Future<void> buildDeleteDialog({required Future<void> Function() onConfirm}) async {
await Get.defaultDialog(
title: 'حذف خرید',
middleText: 'آیا از حذف این خرید مطمئن هستید؟',
@@ -695,7 +703,7 @@ class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
],
),
);
}
}*/
GestureDetector timeFilterWidget({
isFrom = true,

View File

@@ -0,0 +1,346 @@
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_core/core.dart';
import 'empty_widget.dart';
class BuyersPage extends GetView<SalesOutOfProvinceLogic> {
const BuyersPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: 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<int> 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.setEditData(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.isSubmitButtonEnabled);
}
Widget _provinceWidget() {
return Obx(() {
return OverlayDropdownWidget<IranProvinceCityModel>(
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<IranProvinceCityModel>(
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);
}
}

View File

@@ -0,0 +1,8 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
Widget emptyWidget() {
return Center(
child: Text('داده ای دریافت نشد!', style: AppFonts.yekan16.copyWith(color: AppColor.darkGreyDarkHover)),
);
}