feat : sale out of province

This commit is contained in:
2025-07-07 22:50:06 +03:30
parent 84e3f16f1d
commit 3b5d1665d6
16 changed files with 1263 additions and 1412 deletions

View File

@@ -8,12 +8,14 @@ import 'package:rasadyar_chicken/presentation/pages/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class SaleLogic extends GetxController {
Rxn<List<AllocatedMadeModel>?> allocatedMadeModel = Rxn<List<AllocatedMadeModel>?>();
Rxn<List<AllocatedMadeModel>?> allocatedMadeModel =
Rxn<List<AllocatedMadeModel>?>();
RxList<ProductModel> rolesProductsModel = RxList<ProductModel>();
RxList<GuildModel> guildsModel = <GuildModel>[].obs;
Rxn<StewardFreeBarDashboard> stewardFreeDashboard = Rxn<StewardFreeBarDashboard>();
Rxn<StewardFreeBarDashboard> stewardFreeDashboard =
Rxn<StewardFreeBarDashboard>();
RootLogic rootLogic = Get.find<RootLogic>();
@@ -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();

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -19,7 +19,10 @@ class SalesInProvincePage extends GetView<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
],
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<SalesInProvinceLogic> {
],
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<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
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<SalesInProvinceLogic> {
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),
),

View File

@@ -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<RootLogic>();
SaleLogic get saleLogic => Get.find<SaleLogic>();
SalesOutOfProvinceBuyersLogic get buyersLogic =>
Get.find<SalesOutOfProvinceBuyersLogic>();
SalesOutOfProvinceSalesListLogic get saleListLogic =>
Get.find<SalesOutOfProvinceSalesListLogic>();
RxInt selectedSegmentIndex = 0.obs;
RxBool isExpanded = false.obs;
RxBool isBuyerSubmitButtonEnabled = false.obs;
RxBool isSaleSubmitButtonEnabled = false.obs;
RxList<int> isExpandedList = <int>[].obs;
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
RxnString searchedValue = RxnString();
RxList<String> routesName = RxList();
//TODO add this to Di
ImagePicker imagePicker = ImagePicker();
@override
void onInit() {
super.onInit();
routesName.value = [...saleLogic.routesName, 'خارج استان'].toList();
Rx<Resource<List<OutProvinceCarcassesBuyer>>> buyerList =
Resource<List<OutProvinceCarcassesBuyer>>.loading().obs;
Rx<Resource<List<StewardFreeSaleBar>>> salesList =
Resource<List<StewardFreeSaleBar>>.loading().obs;
Rxn<ProductModel> selectedProduct = Rxn();
Rxn<OutProvinceCarcassesBuyer> selectedBuyer = Rxn();
RxList<IranProvinceCityModel> cites = <IranProvinceCityModel>[].obs;
Rxn<IranProvinceCityModel> selectedProvince = Rxn();
Rxn<IranProvinceCityModel> selectedCity = Rxn();
GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController buyerNameController = TextEditingController();
TextEditingController buyerLastNameController = TextEditingController();
TextEditingController buyerPhoneController = TextEditingController();
TextEditingController buyerUnitNameController = TextEditingController();
//Sale
TextEditingController quarantineCodeController = TextEditingController();
TextEditingController saleWeightController = TextEditingController();
Rx<Jalali> 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<void> 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<List<OutProvinceCarcassesBuyer>>.empty();
} else {
buyerList.value = Resource<List<OutProvinceCarcassesBuyer>>.success(res!.results!);
}
},
);
}
Future<void> 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<List<StewardFreeSaleBar>>.empty();
} else {
salesList.value = Resource<List<StewardFreeSaleBar>>.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() {
//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<bool> 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<void> deleteStewardPurchaseOutOfProvince(String key) async {
await safeCall(
call: () => rootLogic.chickenRepository.deleteStewardPurchasesOutSideOfTheProvince(
token: rootLogic.tokenService.accessToken.value!,
stewardFreeBarKey: key,
),
);
}
Future<bool> 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<bool> 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();
}
}
}

View File

@@ -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<SalesOutOfProvinceLogic> {
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<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.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<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

@@ -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<SalesOutOfProvinceLogic> {
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<int> 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<OutProvinceCarcassesBuyer>(
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<ProductModel>(
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<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.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<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: controller.saleDate.value,
mode: PersianCupertinoDatePickerMode.date,
onDateTimeChanged: (dateTime) {
tempPickedDate = dateTime;
},
),
),
),
],
),
);
}
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.getOutProvinceSales());
}
}

View File

@@ -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<SalesOutOfProvinceLogic> {
@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<SalesOutOfProvinceLogic> {
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<SalesOutOfProvinceLogic> {
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<SalesOutOfProvinceLogic> {
),
);
}
}

View File

@@ -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)),
),
);
}

View File

@@ -1,38 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
ObxValue<RxBool> 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);
}

View File

@@ -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<RootLogic>();
SaleLogic get saleLogic => Get.find<SaleLogic>();
SalesOutOfProvinceLogic get saleOutOfProvince =>
Get.find<SalesOutOfProvinceLogic>();
RxInt currentPage = 1.obs;
RxList<int> isExpandedList = <int>[].obs;
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
RxnString searchedValue = RxnString();
RxBool isLoadingMoreAllocationsMade = false.obs;
RxBool isBuyerSubmitButtonEnabled = false.obs;
RxList<IranProvinceCityModel> cites = <IranProvinceCityModel>[].obs;
Rxn<IranProvinceCityModel> selectedProvince = Rxn();
Rxn<IranProvinceCityModel> selectedCity = Rxn();
GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController buyerNameController = TextEditingController();
TextEditingController buyerLastNameController = TextEditingController();
TextEditingController buyerPhoneController = TextEditingController();
TextEditingController buyerUnitNameController = TextEditingController();
String? key;
Rx<Resource<PaginationModel<OutProvinceCarcassesBuyer>>> buyerList =
Resource<PaginationModel<OutProvinceCarcassesBuyer>>.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<void> getOutProvinceCarcassesBuyer([
bool isLoadingMore = false,
]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
buyerList.value =
Resource<PaginationModel<OutProvinceCarcassesBuyer>>.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<PaginationModel<OutProvinceCarcassesBuyer>>.empty();
} else {
buyerList.value =
Resource<PaginationModel<OutProvinceCarcassesBuyer>>.success(
PaginationModel<OutProvinceCarcassesBuyer>(
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<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() {
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<bool> 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;
}
}

View File

@@ -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<SalesOutOfProvinceBuyersLogic> {
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<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);
}
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}',
),
],
),
);
}
}

View File

@@ -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<RootLogic>();
SaleLogic get saleLogic => Get.find<SaleLogic>();
SalesOutOfProvinceBuyersLogic get buyerLogic =>
Get.find<SalesOutOfProvinceBuyersLogic>();
RxInt selectedSegmentIndex = 0.obs;
RxBool isExpanded = false.obs;
RxInt currentPage = 1.obs;
RxBool isSaleSubmitButtonEnabled = false.obs;
RxList<int> isExpandedList = <int>[].obs;
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
RxnString searchedValue = RxnString();
RxList<String> routesName = RxList();
RxBool isLoadingMoreAllocationsMade = false.obs;
Rxn<IranProvinceCityModel> selectedCity = Rxn();
//TODO add this to Di
ImagePicker imagePicker = ImagePicker();
GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController quarantineCodeController = TextEditingController();
TextEditingController saleWeightController = TextEditingController();
Rx<Jalali> saleDate = Jalali.now().obs;
String? key;
Rx<Resource<PaginationModel<StewardFreeSaleBar>>> salesList =
Resource<PaginationModel<StewardFreeSaleBar>>.loading().obs;
Rxn<ProductModel> selectedProduct = Rxn();
Rxn<OutProvinceCarcassesBuyer> 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<void> getOutProvinceSales([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
salesList.value = Resource<PaginationModel<StewardFreeSaleBar>>.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<PaginationModel<StewardFreeSaleBar>>.empty();
} else {
salesList.value =
Resource<PaginationModel<StewardFreeSaleBar>>.success(
PaginationModel<StewardFreeSaleBar>(
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<void> deleteStewardPurchaseOutOfProvince(String key) async {
await safeCall(
call: () => rootLogic.chickenRepository
.deleteStewardPurchasesOutSideOfTheProvince(
token: rootLogic.tokenService.accessToken.value!,
stewardFreeBarKey: key,
),
);
}
Future<bool> 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<bool> 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;
}
}

View File

@@ -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<SalesOutOfProvinceSalesListLogic> {
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<OutProvinceCarcassesBuyer>(
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<ProductModel>(
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<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.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<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: controller.saleDate.value,
mode: PersianCupertinoDatePickerMode.date,
onDateTimeChanged: (dateTime) {
tempPickedDate = dateTime;
},
),
),
),
],
),
);
}
}

View File

@@ -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(

View File

@@ -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<T> with _$PaginationModel<T> {
Map<String, dynamic> json,
T Function(Object?) fromJsonT,
) => _$PaginationModelFromJson(json, fromJsonT);
}