refactor: chicken module for other role's

feat: steward Role
chore: add some rive file and library
This commit is contained in:
2025-09-02 14:40:55 +03:30
parent dfb9298097
commit 5ec772ee8a
49 changed files with 395 additions and 201 deletions

View File

@@ -0,0 +1,23 @@
import 'package:rasadyar_core/core.dart';
class BuyLogic extends GetxController {
List<String> routesName = ['خرید'];
@override
void onInit() {
super.onInit();
}
@override
void onReady() {
// TODO: implement onReady
super.onReady();
}
@override
void onClose() {
// TODO: implement onClose
super.onClose();
}
}

View File

@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/sale_buy_card_item.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class BuyPage extends GetView<BuyLogic> {
const BuyPage({super.key});
@override
Widget build(BuildContext context) {
return BasePage(
routes: controller.routesName,
isBase: true,
widgets: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Wrap(
alignment: WrapAlignment.center,
spacing: 14.w,
children: [
saleOrBuyItemCard(
title: 'خرید داخل استان',
iconPath: Assets.vec.cubeSvg.path,
color: AppColor.greenNormalActive,
onTap: () {
Get.toNamed(ChickenRoutes.buysInProvinceSteward, id: 0);
},
),
saleOrBuyItemCard(
title: 'خرید خارج استان',
iconPath: Assets.vec.truckFastSvg.path,
color: AppColor.greenNormalActive,
onTap: () {
Get.toNamed(ChickenRoutes.buysOutOfProvinceSteward, id: 0);
},
),
],
),
],
),
],
);
}
}

View File

@@ -0,0 +1,84 @@
import 'package:rasadyar_chicken/presentation/pages/steward/buy/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/buy_in_province_all/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/buy_in_province_waiting/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class BuyInProvinceLogic extends GetxController {
RxList<String> routesName = RxList();
RxList<int> isExpandedList = <int>[].obs;
RxnString searchedValue = RxnString();
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
StewardRootLogic get rootLogic => Get.find<StewardRootLogic>();
BuyLogic get buyLogic => Get.find<BuyLogic>();
RxInt selectedSegmentIndex = 0.obs;
BuyInProvinceAllLogic buyAllLogic = Get.find<BuyInProvinceAllLogic>();
BuyInProvinceWaitingLogic buyWaitingLogic = Get.find<BuyInProvinceWaitingLogic>();
@override
void onInit() {
super.onInit();
routesName.value = [...buyLogic.routesName, 'داخل استان'].toList();
routesName.add(selectedSegmentIndex.value == 0 ? 'در انتظار' : 'همه');
ever(selectedSegmentIndex, (callback) {
routesName.removeLast();
routesName.add(callback == 0 ? 'در انتظار' : 'همه');
});
ever(fromDateFilter, (callback) => _setFromDateFilter(callback));
ever(toDateFilter, (callback) => _setToDateFilter(callback));
}
@override
void onReady() {
// TODO: implement onReady
super.onReady();
}
@override
void onClose() {
// TODO: implement onClose
super.onClose();
}
void _setFromDateFilter(Jalali jalali) {
final isWaiting = selectedSegmentIndex.value == 0;
if (isWaiting) {
buyWaitingLogic.fromDateFilter.value = fromDateFilter.value;
} else {
buyAllLogic.fromDateFilter.value = fromDateFilter.value;
}
}
void _setToDateFilter(Jalali jalali) {
final isWaiting = selectedSegmentIndex.value == 0;
if (isWaiting) {
buyWaitingLogic.toDateFilter.value = fromDateFilter.value;
} else {
buyAllLogic.toDateFilter.value = fromDateFilter.value;
}
}
Future<void> submitFilter() async {
final isWaiting = selectedSegmentIndex.value == 0;
if (isWaiting) {
buyWaitingLogic.getWaitingArrivals();
} else {
buyAllLogic.getAllArrivals();
}
}
void setSearchValue(String? data) {
searchedValue.value = data?.trim();
final isWaiting = selectedSegmentIndex.value == 0;
if (isWaiting) {
buyWaitingLogic.searchedValue.value = searchedValue.value;
} else {
buyAllLogic.searchedValue.value = searchedValue.value;
}
}
}

View File

@@ -0,0 +1,96 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/buy_in_province_all/view.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/buy_in_province_waiting/view.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/steward/inventory_widget.dart';
import 'package:rasadyar_chicken/presentation/widget/page_route.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class BuyInProvincePage extends GetView<BuyInProvinceLogic> {
const BuyInProvincePage({super.key});
@override
Widget build(BuildContext context) {
return BasePage(
routesWidget: ObxValue((route) => buildPageRoute(route), controller.routesName),
onBackPressed: () => Get.back(id: 0),
onSearchChanged: (data) => controller.setSearchValue(data),
filteringWidget: filterBottomSheet(),
widgets: [
inventoryWidget(controller.rootLogic),
segmentWidget(),
ObxValue((index) {
return Expanded(
child: index.value == 0 ? BuyInProvinceWaitingPage() : BuyInProvinceAllPage(),
);
}, controller.selectedSegmentIndex),
],
);
}
Padding segmentWidget() {
return Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
child: Row(
children: [
Expanded(
child: RSegment(
children: ['در انتظار', 'بایگانی'],
selectedIndex: 0,
borderColor: const Color(0xFFB4B4B4),
selectedBorderColor: AppColor.blueNormal,
selectedBackgroundColor: AppColor.blueLight,
onSegmentSelected: (index) => controller.selectedSegmentIndex.value = index,
backgroundColor: AppColor.whiteGreyNormal,
),
),
],
),
);
}
Widget filterBottomSheet() {
return BaseBottomSheet(
height: 200,
child: Column(
spacing: 16,
children: [
Text('فیلترها', style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal)),
Row(
spacing: 8,
children: [
Expanded(
child: dateFilterWidget(
date: controller.fromDateFilter,
onChanged: (jalali) => controller.fromDateFilter.value = jalali,
),
),
Expanded(
child: dateFilterWidget(
isFrom: false,
date: controller.toDateFilter,
onChanged: (jalali) => controller.toDateFilter.value = jalali,
),
),
],
),
RElevated(
text: 'اعمال فیلتر',
isFullWidth: true,
backgroundColor: AppColor.greenNormal,
onPressed: () {
controller.submitFilter();
Get.back();
},
height: 40,
),
SizedBox(height: 16),
],
),
);
}
}

View File

@@ -0,0 +1,145 @@
import 'package:rasadyar_chicken/data/models/request/steward_allocation/steward_allocation_request.dart';
import 'package:rasadyar_chicken/data/models/response/waiting_arrival/waiting_arrival.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class BuyInProvinceAllLogic extends GetxController {
RxList<int> isExpandedList = <int>[].obs;
Rxn<Jalali> fromDateFilter = Rxn();
Rxn<Jalali> toDateFilter = Rxn();
RxnString searchedValue = RxnString();
RxMap<String, bool> isLoadingConfirmMap = RxMap();
final RxBool isLoadingMoreAllocationsMade = false.obs;
RxInt currentPage = 1.obs;
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
Rx<Resource<PaginationModel<WaitingArrivalModel>>> allProduct =
Resource<PaginationModel<WaitingArrivalModel>>.loading().obs;
@override
void onInit() {
super.onInit();
getAllArrivals();
}
@override
void onReady() {
debounce(searchedValue, (callback) => getAllArrivals(), time: Duration(milliseconds: 2000));
super.onReady();
}
@override
void onClose() {
// TODO: implement onClose
super.onClose();
}
Future<void> getAllArrivals([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
allProduct.value = Resource<PaginationModel<WaitingArrivalModel>>.loading();
}
if (searchedValue.value != null &&
searchedValue.value!.trim().isNotEmpty &&
currentPage.value > 1) {
currentPage.value = 1;
}
safeCall(
call: () async => await rootLogic.chickenRepository.getWaitingArrivals(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
queryParams: {'type': 'all'},
pageSize: 20,
page: currentPage.value,
search: 'filter',
role: 'Steward',
value: searchedValue.value,
fromDate: fromDateFilter.value?.toDateTime(),
toDate: toDateFilter.value?.toDateTime(),
),
),
onSuccess: (res) async {
await Future.delayed(Duration(milliseconds: 200));
if ((res?.count ?? 0) == 0) {
allProduct.value = Resource<PaginationModel<WaitingArrivalModel>>.empty();
} else {
allProduct.value = Resource<PaginationModel<WaitingArrivalModel>>.success(
PaginationModel<WaitingArrivalModel>(
count: res?.count ?? 0,
next: res?.next,
previous: res?.previous,
results: [...(allProduct.value.data?.results ?? []), ...(res?.results ?? [])],
),
);
}
},
);
}
Future<void> acceptEntries(WaitingArrivalModel model) async {
var request = StewardAllocationRequest(
allocationKey: model.key,
checkAllocation: true,
state: 'accepted',
receiverRealNumberOfCarcasses: model.realNumberOfCarcasses ?? 0,
receiverRealWeightOfCarcasses: model.realWeightOfCarcasses?.toInt() ?? 0,
registrationCode: model.registrationCode ?? 0,
weightLossOfCarcasses: model.weightLossOfCarcasses?.toInt() ?? 0,
).toJson();
request.removeWhere((key, value) => value == null);
safeCall(
call: () async => await rootLogic.chickenRepository.setSateForArrivals(
token: rootLogic.tokenService.accessToken.value!,
request: request,
),
onError: (error, stackTrace) {
eLog(error);
},
onSuccess: (result) {
getAllArrivals();
rootLogic.getInventory();
},
);
}
Future<void> denyEntries(WaitingArrivalModel model) async {
var request = StewardAllocationRequest(
allocationKey: model.key,
checkAllocation: true,
state: 'rejected',
).toJson();
request.removeWhere((key, value) => value == null);
safeCall(
call: () async => await rootLogic.chickenRepository.setSateForArrivals(
token: rootLogic.tokenService.accessToken.value!,
request: request,
),
onError: (error, stackTrace) {
eLog(error);
},
onSuccess: (result) {
getAllArrivals();
rootLogic.getInventory();
},
);
}
String getVecPathItem(String? item) {
switch (item) {
case 'pending':
return Assets.vec.timerSvg.path;
case 'accepted':
return Assets.vec.checkSquareSvg.path;
case 'rejected':
return Assets.vec.closeCircleSvg.path;
default:
return Assets.vec.timerSvg.path;
}
}
}

View File

@@ -0,0 +1,269 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/response/waiting_arrival/waiting_arrival.dart';
import 'package:rasadyar_chicken/presentation/utils/string_utils.dart';
import 'package:rasadyar_chicken/presentation/widget/list_item/list_item.dart' hide ListItem2;
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class BuyInProvinceAllPage extends GetView<BuyInProvinceAllLogic> {
const BuyInProvinceAllPage({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ObxValue((data) {
return RPaginatedListView(
listType: ListType.separated,
resource: data.value,
hasMore: data.value.data?.next != null,
padding: EdgeInsets.fromLTRB(8, 8, 8, 80),
itemBuilder: (context, index) {
var item = data.value.data!.results![index];
return ObxValue((val) {
return ExpandableListItem2(
selected: val.contains(index),
onTap: () => controller.isExpandedList.toggle(index),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item),
labelColor: getLabelColor(item.receiverState),
labelIcon: controller.getVecPathItem(item.receiverState),
);
}, controller.isExpandedList);
},
itemCount: data.value.data?.results?.length ?? 0,
separatorBuilder: (context, index) => SizedBox(height: 8.h),
onLoadMore: () async => controller.getAllArrivals(true),
onRefresh: () async {
controller.currentPage.value = 1;
await controller.getAllArrivals();
},
);
}, controller.allProduct),
);
}
Row itemListWidget(WaitingArrivalModel item) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 20),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 3,
children: [
Text(
item.toSteward?.user?.fullname ?? 'N/A',
textAlign: TextAlign.start,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
Text(
item.date?.formattedJalaliDate ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 3,
child: Column(
spacing: 3,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 6,
children: [
Visibility(
visible: item.product?.name?.contains('مرغ گرم') ?? false,
child: Assets.vec.hotChickenSvg.svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
Text(
'${item.weightOfCarcasses?.separatedByComma}kg',
textAlign: TextAlign.left,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
],
),
Text(
item.toSteward?.guildsName ?? 'N/Aaq',
textAlign: TextAlign.start,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 1,
child: Assets.vec.scanSvg.svg(
width: 32.w,
height: 32.h,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
],
);
}
Container itemListExpandedWidget(WaitingArrivalModel item) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
item.steward?.user?.fullname ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.greenDark),
),
Spacer(),
Text(
item.receiverState?.faItem,
textAlign: TextAlign.center,
style: AppFonts.yekan10.copyWith(color: AppColor.darkGreyDark),
),
SizedBox(width: 7),
SvgGenImage.vec(
controller.getVecPathItem(item.receiverState),
).svg(width: 16.w, height: 16.h,
colorFilter: ColorFilter.mode(AppColor.darkGreyDark, BlendMode.srcIn)
),
],
),
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: ShapeDecoration(
color: AppColor.blueLight,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: AppColor.blueLightHover),
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 3,
children: [
Text(
item.date?.toJalali.formatter.wN ?? 'N/A',
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),
),
],
),
Text(
'${item.date?.toJalali.formatter.y}',
style: AppFonts.yekan20.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.tHH}:${item.date?.toJalali.formatter.tMM ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
buildRow(
title: 'مشخصات فروشنده',
value: item.steward?.user?.fullname ?? 'N/A',
),
buildRow(
title: 'تلفن فروشنده',
value: item.steward?.user?.mobile ?? 'N/A',
valueStyle: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
buildRow(
title: 'نوع تخصیص',
value: item.allocationType?.faAllocationType ?? 'N/A',
),
buildRow(title: 'محصول', value: item.product?.name ?? 'N/A'),
buildRow(
title: 'وزن خریداری شده',
value: '${item.weightOfCarcasses?.separatedByComma} کیلوگرم',
),
buildRow(title: 'قیمت کل', value: '${item.totalAmount?.separatedByComma} ریال'),
Visibility(
visible: item.receiverState == 'pending',
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
ObxValue((data) {
return RElevated(
text: 'تایید',
width: 150.w,
height: 40.h,
isLoading: data[item.key!] ?? false,
onPressed: () async {
data[item.key!] = !(data[item.key!] ?? false);
await controller.acceptEntries(item);
data.remove(item.key!);
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
);
}, controller.isLoadingConfirmMap),
ROutlinedElevated(
text: 'رد',
textStyle: AppFonts.yekan20.copyWith(color: AppColor.redNormal),
width: 150.w,
height: 40.h,
onPressed: () {
buildWarningDialog(
title: 'اخطار',
middleText: 'آیا از رد شدن این مورد اطمینان دارید؟',
onConfirm: () => controller.denyEntries(item),
onRefresh: () => controller.getAllArrivals(),
);
},
borderColor: AppColor.redNormal,
),
],
),
),
],
),
);
}
Color getLabelColor(String? item) {
switch (item) {
case 'pending':
return AppColor.blueLight;
case 'accepted':
return AppColor.greenLightHover;
case 'rejected':
return AppColor.redLightHover;
default:
return AppColor.blueLight;
}
}
}

View File

@@ -0,0 +1,160 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/request/steward_allocation/steward_allocation_request.dart';
import 'package:rasadyar_chicken/data/models/response/waiting_arrival/waiting_arrival.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/root/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
class BuyInProvinceWaitingLogic extends GetxController {
RxList<int> isExpandedList = <int>[].obs;
Rxn<Jalali> fromDateFilter = Rxn();
Rxn<Jalali> toDateFilter = Rxn();
RxnString searchedValue = RxnString();
RxMap<String, bool> isLoadingConfirmMap = RxMap();
Rx<Color> bgConfirmAllColor = AppColor.blueNormal.obs;
RxInt currentPage = 1.obs;
final RxBool isLoadingMoreAllocationsMade = false.obs;
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
Rx<Resource<PaginationModel<WaitingArrivalModel>>> waitingProduct =
Resource<PaginationModel<WaitingArrivalModel>>.loading().obs;
@override
void onInit() {
super.onInit();
debounce(
searchedValue,
(callback) => getWaitingArrivals(),
time: Duration(milliseconds: timeDebounce),
);
}
@override
void onReady() {
super.onReady();
getWaitingArrivals();
}
@override
void onClose() {
super.onClose();
}
void setSearchValue(String? data) {
searchedValue.value = data?.trim();
}
Future<void> getWaitingArrivals([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
waitingProduct.value = Resource<PaginationModel<WaitingArrivalModel>>.loading();
}
if (searchedValue.value != null &&
searchedValue.value!.trim().isNotEmpty &&
currentPage.value > 1) {
currentPage.value = 1;
}
safeCall(
call: () async => await rootLogic.chickenRepository.getWaitingArrivals(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
queryParams: {'type': 'not_entered'},
pageSize: 20,
page: currentPage.value,
search: 'filter',
role: 'Steward',
value: searchedValue.value,
fromDate: fromDateFilter.value?.toDateTime(),
toDate: toDateFilter.value?.toDateTime(),
),
),
onSuccess: (res) async {
await Future.delayed(Duration(milliseconds: 200));
if ((res?.count ?? 0) == 0) {
waitingProduct.value = Resource<PaginationModel<WaitingArrivalModel>>.empty();
} else {
waitingProduct.value = Resource<PaginationModel<WaitingArrivalModel>>.success(
PaginationModel<WaitingArrivalModel>(
count: res?.count ?? 0,
next: res?.next,
previous: res?.previous,
results: [...(waitingProduct.value.data?.results ?? []), ...(res?.results ?? [])],
),
);
flashingFabBgColor();
}
},
);
}
Future<void> acceptEntries(WaitingArrivalModel model) async {
var request = StewardAllocationRequest(
allocationKey: model.key,
checkAllocation: true,
state: 'accepted',
receiverRealNumberOfCarcasses: model.realNumberOfCarcasses ?? 0,
receiverRealWeightOfCarcasses: model.realWeightOfCarcasses?.toInt() ?? 0,
registrationCode: model.registrationCode ?? 0,
weightLossOfCarcasses: model.weightLossOfCarcasses?.toInt() ?? 0,
).toJson();
request.removeWhere((key, value) => value == null);
safeCall(
call: () async => await rootLogic.chickenRepository.setSateForArrivals(
token: rootLogic.tokenService.accessToken.value!,
request: request,
),
onError: (error, stackTrace) {
eLog(error);
},
onSuccess: (result) {
getWaitingArrivals();
// getBarGeneralInformation();
},
);
}
Future<void> denyEntries(WaitingArrivalModel model) async {
var request = StewardAllocationRequest(
allocationKey: model.key,
checkAllocation: true,
state: 'rejected',
).toJson();
request.removeWhere((key, value) => value == null);
safeCall(
call: () async => await rootLogic.chickenRepository.setSateForArrivals(
token: rootLogic.tokenService.accessToken.value!,
request: request,
),
onError: (error, stackTrace) {
eLog(error);
},
onSuccess: (result) {
getWaitingArrivals();
//TODO
// getBarGeneralInformation();
},
);
}
void flashingFabBgColor() {
Timer.periodic(Duration(seconds: 2), (timer) {
if (bgConfirmAllColor.value == AppColor.blueNormal) {
bgConfirmAllColor.value = AppColor.blueLightHover;
} else {
bgConfirmAllColor.value = AppColor.blueNormal;
}
});
}
}

View File

@@ -0,0 +1,264 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/response/waiting_arrival/waiting_arrival.dart';
import 'package:rasadyar_chicken/presentation/utils/string_utils.dart';
import 'package:rasadyar_chicken/presentation/widget/list_item/list_item.dart' hide ListItem2;
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class BuyInProvinceWaitingPage extends GetView<BuyInProvinceWaitingLogic> {
const BuyInProvinceWaitingPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ObxValue((data) {
return RPaginatedListView(
listType: ListType.separated,
resource: data.value,
hasMore: data.value.data?.next != null,
padding: EdgeInsets.fromLTRB(8, 8, 8, 80),
itemBuilder: (context, index) {
var item = data.value.data!.results![index];
return ObxValue((val) {
return ExpandableListItem2(
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),
onLoadMore: () async => controller.getWaitingArrivals(true),
onRefresh: () async {
controller.currentPage.value = 1;
await controller.getWaitingArrivals();
},
);
}, controller.waitingProduct),
),
/* floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: ObxValue((data) {
if ((data.value.data?.results?.length ?? 0) > 1) {
return AnimatedFab(
onPressed: () {
//TODO FAB
},
message: 'تایید یکجا',
icon: Assets.vec.clipboardTaskSvg.svg(width: 45.w, height: 42.h),
backgroundColor: controller.bgConfirmAllColor.value,
);
} else {
return SizedBox.shrink();
}
}, controller.waitingProduct),*/
);
}
Row itemListWidget(WaitingArrivalModel item) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 20),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 3,
children: [
Text(
item.steward?.user?.fullname ?? 'N/A',
textAlign: TextAlign.start,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
Text(
item.date?.formattedJalaliDate ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 3,
child: Column(
spacing: 3,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 6,
children: [
Visibility(
visible: item.product?.name?.contains('مرغ گرم') ?? false,
child: Assets.vec.hotChickenSvg.svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
Text(
'${item.weightOfCarcasses?.separatedByComma}kg',
textAlign: TextAlign.left,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
],
),
Text(
item.steward?.guildsName ?? 'N/A',
textAlign: TextAlign.start,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 1,
child: Assets.vec.scanSvg.svg(
width: 32.w,
height: 32.h,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
],
);
}
Container itemListExpandedWidget(WaitingArrivalModel item) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
item.steward?.user?.fullname ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.greenDark),
),
Spacer(),
Text(
'در انتظار',
textAlign: TextAlign.center,
style: AppFonts.yekan10.copyWith(color: AppColor.darkGreyDark),
),
SizedBox(width: 7),
Assets.vec.clockSvg.svg(width: 16.w, height: 16.h),
],
),
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: ShapeDecoration(
color: AppColor.blueLight,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: AppColor.blueLightHover),
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 3,
children: [
Text(
item.date?.toJalali.formatter.wN ?? 'N/A',
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),
),
],
),
Text(
'${item.date?.toJalali.formatter.y}',
style: AppFonts.yekan20.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.tHH}:${item.date?.toJalali.formatter.tMM ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
buildRow(
title: 'مشخصات فروشنده',
value: item.steward?.user?.fullname ?? 'N/A',
),
buildRow(
title: 'تلفن فروشنده',
value: item.steward?.user?.mobile ?? 'N/A',
valueStyle: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
buildRow(
title: 'نوع تخصیص',
value: item.allocationType?.faAllocationType ?? 'N/A',
),
buildRow(title: 'محصول', value: item.product?.name ?? 'N/A'),
buildRow(
title: 'وزن خریداری شده',
value: '${item.weightOfCarcasses?.separatedByComma} کیلوگرم',
),
buildRow(title: 'قیمت کل', value: '${item.totalAmount?.separatedByComma} ریال'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
ObxValue((data) {
return RElevated(
text: 'تایید',
width: 150.w,
height: 40.h,
isLoading: data[item.key!] ?? false,
onPressed: () async {
data[item.key!] = !(data[item.key!] ?? false);
await controller.acceptEntries(item);
data.remove(item.key!);
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
);
}, controller.isLoadingConfirmMap),
ROutlinedElevated(
text: 'رد',
textStyle: AppFonts.yekan20.copyWith(color: AppColor.redNormal),
width: 150.w,
height: 40.h,
onPressed: () {
buildWarningDialog(
title: 'اخطار',
middleText: 'آیا از رد شدن این مورد اطمینان دارید؟',
onConfirm: () => controller.denyEntries(item),
onRefresh: () => controller.getWaitingArrivals(),
);
},
borderColor: AppColor.redNormal,
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,239 @@
import 'package:flutter/cupertino.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/steward/buy/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/root/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/sale/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
class BuyOutOfProvinceLogic extends GetxController {
late List<String> routesName;
RxBool isExpanded = false.obs;
RxBool isSubmitButtonEnabled = false.obs;
RxList<int> isExpandedList = <int>[].obs;
final RxInt currentPage = 1.obs;
final RxBool isLoadingMoreAllocationsMade = false.obs;
final RxBool isOnLoadingSubmitOrEdit = false.obs;
//TODO add this to Di
ImagePicker imagePicker = ImagePicker();
Rx<Resource<PaginationModel<StewardFreeBar>>> purchaseOutOfProvinceList =
Resource<PaginationModel<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();
StewardRootLogic get rootLogic => Get.find<StewardRootLogic>();
BuyLogic get buyLogic => Get.find<BuyLogic>();
SaleLogic get outOfTheProvinceLogic => Get.find<SaleLogic>();
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 onInit() {
super.onInit();
routesName = [...buyLogic.routesName, 'خارج استان'].toList();
}
@override
void onReady() {
super.onReady();
getStewardPurchaseOutOfProvince();
selectedProvince.listen((p0) => getCites());
selectedProduct.value = rootLogic.rolesProductsModel.first;
setupListeners();
debounce(
searchedValue,
(callback) => getStewardPurchaseOutOfProvince(),
time: Duration(milliseconds: timeDebounce),
);
}
@override
void onClose() {
sellerNameController.dispose();
sellerPhoneController.dispose();
carcassWeightController.dispose();
isExpandedList.clear();
super.onClose();
}
void setSearchValue(String? data) {
searchedValue.value = data?.trim();
}
Future<void> getStewardPurchaseOutOfProvince([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
purchaseOutOfProvinceList.value = Resource<PaginationModel<StewardFreeBar>>.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.getStewardPurchasesOutSideOfTheProvince(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
pageSize: 20,
page: currentPage.value,
search: 'filter',
role: 'Steward',
value: searchedValue.value,
fromDate: fromDateFilter.value.toDateTime(),
toDate: toDateFilter.value.toDateTime(),
),
),
onSuccess: (res) async {
await Future.delayed(Duration(milliseconds: 500));
if ((res?.count ?? 0) == 0) {
purchaseOutOfProvinceList.value = Resource<PaginationModel<StewardFreeBar>>.empty();
} else {
purchaseOutOfProvinceList.value = Resource<PaginationModel<StewardFreeBar>>.success(
PaginationModel<StewardFreeBar>(
count: res?.count ?? 0,
next: res?.next,
previous: res?.previous,
results: [
...(purchaseOutOfProvinceList.value.data?.results ?? []),
...(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;
isOnLoadingSubmitOrEdit.value = true;
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;
},
);
isOnLoadingSubmitOrEdit.value = false;
return res;
}
void resetSubmitForm() {
sellerNameController.clear();
sellerPhoneController.clear();
carcassWeightController.clear();
selectedProvince.value = null;
selectedCity.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 = rootLogic.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

@@ -0,0 +1,596 @@
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/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/steward/inventory_widget.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class BuyOutOfProvincePage extends GetView<BuyOutOfProvinceLogic> {
const BuyOutOfProvincePage({super.key});
@override
Widget build(BuildContext context) {
return BasePage(
routes: controller.routesName,
onBackPressed: () => Get.back(id: 0),
onSearchChanged: (data) => controller.setSearchValue(data),
filteringWidget: filterBottomSheet(),
widgets: [
inventoryWidget(controller.rootLogic),
Expanded(
child: ObxValue((data) {
return RPaginatedListView(
listType: ListType.separated,
resource: data.value,
hasMore: data.value.data?.next != null,
padding: EdgeInsets.fromLTRB(8, 8, 8, 80),
itemBuilder: (context, index) {
var item = data.value.data!.results![index];
return ObxValue((val) {
return ExpandableListItem2(
selected: val.contains(index),
onTap: () => controller.isExpandedList.toggle(index),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item),
labelColor: AppColor.blueLight,
labelIcon: Assets.vec.truckFastOutlinedSvg.path,
);
}, controller.isExpandedList);
},
itemCount: data.value.data?.results?.length ?? 0,
separatorBuilder: (context, index) => SizedBox(height: 8.h),
onLoadMore: () async => controller.getStewardPurchaseOutOfProvince(true),
onRefresh: () async {
controller.currentPage.value = 1;
await controller.getStewardPurchaseOutOfProvince();
},
);
}, controller.purchaseOutOfProvinceList),
),
],
floatingActionButton: RFab.add(
onPressed: () {
Get.bottomSheet(
addPurchasedInformationBottomSheet(),
isScrollControlled: true,
ignoreSafeArea: false,
).whenComplete(() {
controller.resetSubmitForm();
},);
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
);
}
Container itemListExpandedWidget(StewardFreeBar item) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'${item.province}-${item.city}',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.greenDark),
),
],
),
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: ShapeDecoration(
color: AppColor.blueLight,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: AppColor.blueLightHover),
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 3,
children: [
Text(
item.date?.toJalali.formatter.wN ?? 'N/A',
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),
),
],
),
Text(
'${item.date?.toJalali.formatter.y}',
style: AppFonts.yekan20.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.tHH}:${item.date?.toJalali.formatter.tMM ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
buildRow(title: 'مشخصات فروشنده', value: item.killHouseName ?? 'N/A'),
buildRow(
title: 'تلفن فروشنده',
value: item.killHouseMobile ?? 'N/A',
valueStyle: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
buildRow(title: 'محصول', value: item.product?.name ?? 'N/A'),
buildRow(
title: 'وزن خریداری شده',
value: '${item.weightOfCarcasses?.separatedByComma} کیلوگرم',
),
buildRowOnTapped(
title: 'مشاهده بارنامه',
titleStyle: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
valueWidget: Assets.vec.clipboardEyeSvg.svg(
width: 20,
height: 24,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
onTap: () {
Get.bottomSheet(
BaseBottomSheet(
height: 400,
child: Column(
spacing: 16,
children: [
Text(
'بارنامه',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
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;
return CupertinoActivityIndicator();
},
),
],
),
),
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
RElevated(
text: 'ویرایش',
width: 150.w,
height: 40.h,
onPressed: () {
controller.setEditData(item);
Get.bottomSheet(
addPurchasedInformationBottomSheet(true),
isScrollControlled: true,
).whenComplete(() {
controller.resetSubmitForm();
});
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
),
ROutlinedElevated(
text: 'حذف',
textStyle: AppFonts.yekan20.copyWith(color: AppColor.redNormal),
width: 150.w,
height: 40.h,
onPressed: () {
buildDeleteDialog(
onConfirm: () => controller.deleteStewardPurchaseOutOfProvince(item.key!),
onRefresh: () => controller.getStewardPurchaseOutOfProvince(),
);
},
borderColor: AppColor.redNormal,
),
],
),
],
),
);
}
Row itemListWidget(StewardFreeBar item) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 20),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 3,
children: [
Text(
item.killHouseName ?? 'N/A',
textAlign: TextAlign.start,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
Text(
item.date?.formattedJalaliDate ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 3,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 3,
children: [
Visibility(
visible: item.product?.name?.contains('مرغ گرم') ?? false,
child: Assets.vec.hotChickenSvg.svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
Text(
'${item.weightOfCarcasses?.separatedByComma}kg',
textAlign: TextAlign.left,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
],
),
SizedBox(height: 2),
Text(
'${item.province}',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 1,
child: Assets.vec.scanSvg.svg(
width: 32.w,
height: 32.h,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
],
);
}
Widget addPurchasedInformationBottomSheet([bool isOnEdit = false]) {
return BaseBottomSheet(
child: Form(
key: controller.formKey,
child: Column(
spacing: 8,
children: [
Text(
isOnEdit ? 'ویرایش اطلاعات خرید' : 'ثبت اطلاعات خرید',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
_productDropDown(),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(spacing: 12, children: [_provinceWidget(), _cityWidget()]),
),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 12,
children: [
RTextField(
controller: controller.sellerNameController,
label: 'نام فروشنده',
borderColor: AppColor.darkGreyLight,
filled: true,
filledColor: AppColor.bgLight,
),
RTextField(
controller: controller.sellerPhoneController,
label: 'تلفن فروشنده',
keyboardType: TextInputType.phone,
borderColor: AppColor.darkGreyLight,
maxLength: 11,
filled: true,
filledColor: AppColor.bgLight,
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;
},
),
UnitTextField(
controller: controller.carcassWeightController,
hint: 'وزن',
unit: 'کیلوگرم',
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
),
],
),
),
_imageCarcasesWidget(isOnEdit),
submitButtonWidget(isOnEdit),
SizedBox(),
],
),
),
);
}
Widget submitButtonWidget(bool isOnEdit) {
return Obx(() {
return RElevated(
text: isOnEdit ? 'ویرایش' : 'ثبت',
width: Get.width,
backgroundColor: AppColor.greenNormal,
isLoading: controller.isOnLoadingSubmitOrEdit.value,
onPressed: controller.isSubmitButtonEnabled.value
? () async {
var res = await controller.createStewardPurchaseOutOfProvince();
if (res) {
Get.back();
}
}
: null,
height: 40,
);
});
}
Widget _productDropDown() {
return Obx(() {
return OverlayDropdownWidget<ProductModel>(
items: controller.rootLogic.rolesProductsModel,
height: 56,
hasDropIcon: false,
background: Colors.white,
onChanged: (value) {
controller.selectedProduct.value = value;
},
selectedItem: controller.selectedProduct.value,
initialValue: controller.selectedProduct.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Row(
spacing: 8,
children: [
(item?.name?.contains('مرغ گرم') ?? false)
? Assets.images.chicken.image(width: 40, height: 40)
: Assets.vec.placeHolderSvg.svg(width: 40, height: 40),
Text(item?.name ?? 'انتخاب محصول'),
Spacer(),
Text(
'موجودی:${controller.rootLogic.inventoryModel.value?.totalRemainWeight.separatedByComma ?? 0}',
),
],
),
);
});
}
Widget _provinceWidget() {
return Obx(() {
return OverlayDropdownWidget<IranProvinceCityModel>(
items: controller.rootLogic.provinces,
onChanged: (value) {
controller.selectedProvince.value = value;
},
selectedItem: controller.selectedProvince.value,
itemBuilder: (item) => Text(
item.name ?? 'بدون نام',
style: AppFonts.yekan14.copyWith(color: AppColor.lightGreyDarker),
),
labelBuilder: (item) => item?.name != null
? Text(item!.name!, style: AppFonts.yekan14.copyWith(color: AppColor.textColor))
: Text(
'انتخاب استان',
style: AppFonts.yekan14.copyWith(color: AppColor.textColorLight),
),
);
});
}
Widget _cityWidget() {
return ObxValue((data) {
return OverlayDropdownWidget<IranProvinceCityModel>(
items: data,
onChanged: (value) {
controller.selectedCity.value = value;
},
selectedItem: controller.selectedCity.value,
itemBuilder: (item) => Text(
item.name ?? 'بدون نام',
style: AppFonts.yekan14.copyWith(color: AppColor.lightGreyDarker),
),
labelBuilder: (item) => item?.name != null
? Text(item!.name!, style: AppFonts.yekan14.copyWith(color: AppColor.textColor))
: Text('انتخاب شهر', style: AppFonts.yekan14.copyWith(color: AppColor.textColorLight)),
);
}, controller.cites);
}
SizedBox _imageCarcasesWidget(bool isOnEdit) {
return SizedBox(
width: Get.width,
height: 370,
child: Card(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Column(
spacing: 8,
children: [
Text('بارنامه', style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal)),
Expanded(
child: ObxValue((data) {
return Container(
width: Get.width,
height: 270,
decoration: BoxDecoration(
color: AppColor.lightGreyNormal,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 1, color: AppColor.blackLight),
),
child: Center(
child: isOnEdit
? Image.network(controller.editImageUrl.value ?? '')
: data.value == null
? Padding(
padding: const EdgeInsets.fromLTRB(30, 10, 10, 30),
child: Assets.vec.placeHolderSvg.svg(width: 200.w, height: 150.h),
)
: Image.file(File(data.value!.path), fit: BoxFit.cover),
),
);
}, controller.selectedImage),
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
RElevated(
text: 'گالری',
width: 150.w,
height: 40.h,
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
onPressed: () async {
controller.selectedImage.value = await controller.imagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 60,
maxWidth: 1080,
maxHeight: 720,
);
},
),
SizedBox(width: 16),
ROutlinedElevated(
text: 'دوربین',
width: 150.w,
height: 40.h,
textStyle: AppFonts.yekan20.copyWith(color: AppColor.blueNormal),
onPressed: () async {
controller.selectedImage.value = await controller.imagePicker.pickImage(
source: ImageSource.camera,
imageQuality: 60,
maxWidth: 1080,
maxHeight: 720,
);
},
),
],
),
],
),
),
),
);
}
Widget filterBottomSheet() {
return BaseBottomSheet(
height: 200,
child: Column(
spacing: 16,
children: [
Text('فیلترها', style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal)),
Row(
spacing: 8,
children: [
Expanded(
child: dateFilterWidget(
date: controller.fromDateFilter,
onChanged: (jalali) => controller.fromDateFilter.value = jalali,
),
),
Expanded(
child: dateFilterWidget(
isFrom: false,
date: controller.toDateFilter,
onChanged: (jalali) => controller.toDateFilter.value = jalali,
),
),
],
),
RElevated(
text: 'اعمال فیلتر',
isFullWidth: true,
backgroundColor: AppColor.greenNormal,
onPressed: () {
controller.getStewardPurchaseOutOfProvince();
Get.back();
},
height: 40,
),
],
),
);
}
}

View File

@@ -0,0 +1,71 @@
import 'package:rasadyar_chicken/chicken.dart';
import 'package:rasadyar_chicken/data/models/response/bar_information/bar_information.dart';
import 'package:rasadyar_chicken/data/models/response/kill_house_distribution_info/kill_house_distribution_info.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class HomeLogic extends GetxController {
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
RxnInt totalWeightTodayBars = RxnInt();
Rxn<KillHouseDistributionInfo> killHouseDistributionInfo = Rxn<KillHouseDistributionInfo>();
Rxn<BarInformation> barInformation = Rxn();
RxBool isExpanded = false.obs;
@override
void onReady() {
super.onReady();
getGeneralBarsInformation();
getTodayBars();
getDistributionInformation();
}
Future<void> getGeneralBarsInformation() async {
await safeCall<BarInformation?>(
call: () async => await rootLogic.chickenRepository.getGeneralBarInformation(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(role: 'Steward'),
),
onSuccess: (result) {
if (result != null) {
barInformation.value = result;
}
},
onError: (error, stackTrace) {},
);
}
Future<void> getTodayBars() async {
await safeCall<BarInformation?>(
call: () async => await rootLogic.chickenRepository.getGeneralBarInformation(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
fromDate: DateTime.now(),
toDate: DateTime.now(),
role: 'Steward',
),
),
onSuccess: (result) {
if (result != null) {
totalWeightTodayBars.value = result.totalBarsWeight?.toInt();
}
},
onError: (error, stackTrace) {},
);
}
Future<void> getDistributionInformation() async {
await safeCall<KillHouseDistributionInfo?>(
call: () async => await rootLogic.chickenRepository.getKillHouseDistributionInfo(
token: rootLogic.tokenService.accessToken.value!,
),
onSuccess: (result) {
if (result != null) {
killHouseDistributionInfo.value = result;
}
},
onError: (error, stackTrace) {},
);
}
}

View File

@@ -0,0 +1,636 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/response/inventory/inventory_model.dart';
import 'package:rasadyar_chicken/data/models/response/kill_house_distribution_info/kill_house_distribution_info.dart';
import 'package:rasadyar_chicken/presentation/widget/app_bar.dart';
import 'package:rasadyar_chicken/presentation/widget/steward/widely_used/view.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class HomePage extends GetView<HomeLogic> {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.bgLight,
appBar: chickenAppBar(hasBack: false, hasFilter: false, hasSearch: false),
body: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Column(
spacing: 8,
children: [
InkWell(
onTap: () {
controller.isExpanded.value = !controller.isExpanded.value;
},
child: Card(
margin: EdgeInsetsGeometry.all(6),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(width: 0.50, color: const Color(0xFFA9A9A9)),
),
child: ObxValue((data) {
return AnimatedSize(
duration: Duration(milliseconds: 300),
child: data.value
? Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
spacing: 8,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 40,
height: 40,
decoration: ShapeDecoration(
image: DecorationImage(
image: AssetImage(Assets.images.chicken.path),
fit: BoxFit.cover,
),
shape: RoundedRectangleBorder(
side: BorderSide(
width: 0.25,
color: const Color(0xFFB0B0B0),
),
borderRadius: BorderRadius.circular(4),
),
),
),
Text(
'مرغ گرم',
textAlign: TextAlign.right,
style: AppFonts.yekan16.copyWith(
color: AppColor.darkGreyDarkActive,
),
),
Spacer(),
AnimatedRotation(
turns: 180,
duration: Duration(milliseconds: 3000),
child: Icon(CupertinoIcons.chevron_up, size: 18),
),
],
),
SizedBox(height: 8),
_todayShipmentWidget(),
_inventoryWidget(),
Row(
children: [
Text(
'اطلاعات بارها',
textAlign: TextAlign.right,
style: AppFonts.yekan16,
),
],
),
_informationShipment(),
Row(
children: [
Text(
'اطلاعات توزیع',
textAlign: TextAlign.right,
style: AppFonts.yekan16,
),
],
),
distributionInformationWidget(),
],
),
)
: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
spacing: 8,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 40,
height: 40,
decoration: ShapeDecoration(
image: DecorationImage(
image: AssetImage(Assets.images.chicken.path),
fit: BoxFit.cover,
),
shape: RoundedRectangleBorder(
side: BorderSide(
width: 0.25,
color: const Color(0xFFB0B0B0),
),
borderRadius: BorderRadius.circular(4),
),
),
),
Text(
'مرغ گرم',
textAlign: TextAlign.right,
style: AppFonts.yekan16.copyWith(
color: AppColor.darkGreyDarkActive,
),
),
Spacer(),
Icon(CupertinoIcons.chevron_down, size: 18),
],
),
_todayShipmentWidget(),
_inventoryWidget(),
],
),
),
);
}, controller.isExpanded),
),
),
WidelyUsedWidget(),
SizedBox(height: 20),
],
),
),
);
}
Widget distributionInformationWidget() {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 13),
child: ObxValue((data) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 8,
children: [
Expanded(
child: _informationIconCard(
title: 'توزیع داخل استان',
isLoading: data.value == null,
description: data.value?.freeSalesWeight.separatedByComma ?? '0',
iconPath: Assets.vec.truckSvg.path,
iconColor: const Color.fromRGBO(85, 97, 93, 1),
bgDescriptionColor: const Color(0xFFE6FAF5),
bgLabelColor: const Color(0xFFB0EFDF),
),
),
Expanded(
child: _informationIconCard(
title: 'توزیع خارج استان',
isLoading: data.value == null,
description: data.value?.stewardAllocationsWeight.separatedByComma ?? '0',
iconPath: Assets.vec.truckFastSvg.path,
iconColor: Color(0xFF647379),
bgDescriptionColor: const Color(0xFFEAEFFF),
bgLabelColor: const Color(0xFFD4DEFF),
),
),
Expanded(
child: _informationIconCard(
title: 'قطعه بندی',
description: '2،225،256',
iconPath: Assets.vec.convertCubeSvg.path,
iconColor: const Color(0xFF6F6164),
bgDescriptionColor: const Color(0xFFEDDCE0),
bgLabelColor: const Color(0xFFE0BCC5),
),
),
],
);
}, controller.killHouseDistributionInfo),
);
}
Widget _informationShipment() {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 13),
child: ObxValue((data) {
return Row(
spacing: 8,
children: [
Expanded(
child: _informationLabelCard(
title: 'داخل استان',
isLoading: data.value == null,
description: data.value != null
? ((data.value?.provinceGovernmentalCarcassesWeight ?? 0) +
(data.value?.provinceFreeCarcassesWeight ?? 0))
.separatedByComma
: '0',
iconPath: Assets.vec.a3dCubeSquareSvg.path,
iconColor: const Color(0xFF6C5D60),
bgDescriptionColor: const Color(0xFFEDDCE0),
bgLabelColor: const Color(0xFFDDC0C7),
),
),
Expanded(
child: _informationLabelCard(
title: 'خارج استان',
isLoading: data.value == null,
description: data.value?.freeBuyingCarcassesWeight.separatedByComma ?? '0',
iconPath: Assets.vec.cubeSearchSvg.path,
iconColor: Color(0xFF2D5FFF),
bgLabelColor: const Color(0xFFAFCBFF),
bgDescriptionColor: const Color(0xFFCEDFFF),
),
),
],
);
}, controller.rootLogic.inventoryModel),
);
}
Widget _inventoryWidget() {
return ObxValue((data) {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 13),
child: Row(
spacing: 8,
children: [
Expanded(
child: _informationLabelCard(
title: 'مانده انبار',
isLoading: data.value == null,
description: data.value?.totalRemainWeight.separatedByComma ?? '0',
iconPath: Assets.vec.cubeSearchSvg.path,
iconColor: const Color(0xFF426060),
bgDescriptionColor: const Color(0xFFC7DFE0),
bgLabelColor: const Color(0xFFA5D1D2),
),
),
Expanded(
child: _informationLabelCard(
title: 'توزیع شده',
isLoading: data.value == null,
description: data.value?.realAllocatedWeight.separatedByComma ?? '0',
iconPath: Assets.vec.cubeRotateSvg.path,
iconColor: Color(0xFF5C4D64),
bgLabelColor: Color(0xFFC8B8D1),
bgDescriptionColor: Color(0xFFDAD4DD),
),
),
],
),
);
}, controller.rootLogic.inventoryModel);
}
Widget _todayShipmentWidget() {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 13),
child: Row(
spacing: 8,
children: [
Expanded(
child: ObxValue(
(data) => _informationLabelCard(
title: 'بارهای امروز',
titleColor: AppColor.blueNormal,
isLoading: data.value == null,
description: data.value?.separatedByComma ?? '0',
iconPath: Assets.vec.cubeSearchSvg.path,
iconColor: AppColor.blueNormal,
bgDescriptionColor: Colors.white,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [AppColor.blueLight, Colors.white],
),
),
controller.totalWeightTodayBars,
),
),
Expanded(
child: ObxValue((data) {
return _informationLabelCard(
title: 'درانتظار تایید',
isLoading: data.value == null,
description: data.value?.totalNotEnteredBars.separatedByComma ?? '0',
unit:
'(${data.value?.totalNotEnteredKillHouseRequestsWeight.separatedByComma})\nکیلوگرم',
iconPath: Assets.vec.cubeWattingSvg.path,
bgDescriptionColor: Colors.white,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [const Color(0xFFFFE7BB), Colors.white],
),
);
}, controller.barInformation),
),
],
),
);
}
Container _informationLabelCard({
required String title,
required String description,
required String iconPath,
required Color bgDescriptionColor,
String unit = 'کیلوگرم',
bool isLoading = false,
Color? iconColor,
Color? titleColor,
Color? bgLabelColor,
LinearGradient? gradient,
}) {
return Container(
height: 82,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
clipBehavior: Clip.hardEdge,
child: Row(
children: [
// Left side with icon and title
Expanded(
child: Container(
height: 82,
decoration: BoxDecoration(
color: gradient == null ? bgLabelColor : null,
borderRadius: BorderRadius.only(
topRight: Radius.circular(8),
bottomRight: Radius.circular(8),
),
gradient: gradient,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
SvgGenImage.vec(iconPath).svg(
width: 24,
height: 24,
colorFilter: iconColor != null
? ColorFilter.mode(iconColor, BlendMode.srcIn)
: null,
),
Text(
title,
textAlign: TextAlign.right,
style: AppFonts.yekan14.copyWith(
color: titleColor ?? AppColor.mediumGreyDarkActive,
),
),
],
),
),
),
// Right side with description and unit
Expanded(
child: Container(
decoration: BoxDecoration(
color: bgDescriptionColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
bottomLeft: Radius.circular(8),
),
),
child: isLoading
? Center(child: CupertinoActivityIndicator())
: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
description,
textAlign: TextAlign.right,
style: AppFonts.yekan16.copyWith(color: AppColor.mediumGreyDarkActive),
),
Text(
unit,
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.mediumGreyDarkActive),
),
],
),
),
),
],
),
);
}
Container _informationIconCard({
required String title,
required String description,
String unit = 'کیلوگرم',
bool isLoading = false,
required String iconPath,
required Color iconColor,
required Color bgDescriptionColor,
required Color bgLabelColor,
}) {
return Container(
height: 110,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
clipBehavior: Clip.hardEdge,
child: Stack(
alignment: Alignment.topCenter,
children: [
Positioned(
bottom: 0,
right: 0,
left: 0,
child: Container(
height: 91,
decoration: BoxDecoration(
color: bgDescriptionColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 0.25, color: const Color(0xFFB4B4B4)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
title,
textAlign: TextAlign.right,
style: AppFonts.yekan14.copyWith(color: AppColor.mediumGreyDarkActive),
),
isLoading
? Center(child: CupertinoActivityIndicator())
: Text(
description,
textAlign: TextAlign.right,
style: AppFonts.yekan16.copyWith(color: AppColor.mediumGreyDarkActive),
),
Text(
unit,
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.mediumGreyDarkActive),
),
],
),
),
),
Positioned(
top: 0,
child: Container(
width: 32,
height: 32,
decoration: ShapeDecoration(
color: bgLabelColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
side: BorderSide(width: 0.25, color: const Color(0xFFD5D5D5)),
),
),
child: Center(
child: SvgGenImage.vec(iconPath).svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn),
),
),
),
),
],
),
);
}
Widget inventoryItem({
required bool isExpanded,
required int index,
required InventoryModel model,
}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 8,
children: [
buildRow('نام محصول', model.name ?? ''),
Visibility(
visible: isExpanded,
child: Column(
spacing: 8,
children: [
buildRow('وزن خریدهای دولتی داخل استان (کیلوگرم)', '0326598653'),
buildRow(
'وزن خریدهای آزاد داخل استان (کیلوگرم)',
model.receiveFreeCarcassesWeight.toString(),
),
buildRow(
'وزن خریدهای خارج استان (کیلوگرم)',
model.freeBuyingCarcassesWeight.toString(),
),
buildRow(
'کل ورودی به انبار (کیلوگرم)',
model.totalFreeBarsCarcassesWeight.toString(),
),
buildRow('کل فروش (کیلوگرم)', model.realAllocatedWeight.toString()),
buildRow('مانده انبار (کیلوگرم)', model.totalRemainWeight.toString()),
],
),
),
],
);
}
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: 1,
child: Text(
value,
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.darkGreyDarkHover),
),
),
],
),
);
}
Widget broadcastInformationWidget(KillHouseDistributionInfo? model) {
return Container(
height: 140,
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.blueNormal, width: 1),
),
child: model != null
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 10,
children: [
Text(
'اطلاعات ارسالی',
textAlign: TextAlign.right,
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
const SizedBox(height: 12),
buildRow(
'فروش و توزیع داخل استان (کیلوگرم)',
model.stewardAllocationsWeight!.toInt().toString(),
),
buildRow(
'فروش و توزیع خارج استان (کیلوگرم)',
model.freeSalesWeight!.toInt().toString(),
),
],
)
: const Center(child: CircularProgressIndicator()),
);
}
Widget cardWidget({
required String title,
required String iconPath,
required VoidCallback onTap,
}) {
return Container(
width: Get.width / 4,
height: 130,
child: GestureDetector(
onTap: onTap,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(width: 1, color: AppColor.blueNormal),
),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SvgGenImage(iconPath).svg(width: 50, height: 50),
SizedBox(height: 4),
Text(
title,
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,149 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/request/change_password/change_password_request_model.dart';
import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart';
import 'package:rasadyar_chicken/data/models/response/user_profile/user_profile.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class ProfileLogic extends GetxController {
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
RxInt selectedInformationType = 0.obs;
Rxn<Jalali> birthDate = Rxn<Jalali>();
Rx<Resource<UserProfile>> userProfile = Rx<Resource<UserProfile>>(Resource.loading());
TextEditingController nameController = TextEditingController();
TextEditingController lastNameController = TextEditingController();
TextEditingController nationalCodeController = TextEditingController();
TextEditingController nationalIdController = TextEditingController();
TextEditingController birthdayController = TextEditingController();
TextEditingController oldPasswordController = TextEditingController();
TextEditingController newPasswordController = TextEditingController();
TextEditingController retryNewPasswordController = TextEditingController();
RxList<IranProvinceCityModel> cites = <IranProvinceCityModel>[].obs;
Rxn<IranProvinceCityModel> selectedProvince = Rxn();
Rxn<IranProvinceCityModel> selectedCity = Rxn();
GlobalKey<FormState> formKey = GlobalKey();
ImagePicker imagePicker = ImagePicker();
Rxn<XFile> selectedImage = Rxn<XFile>();
RxnString _base64Image = RxnString();
RxBool isOnLoading = false.obs;
@override
void onInit() {
super.onInit();
ever(selectedImage, (data) async {
if (data?.path != null) {
_base64Image.value = await convertImageToBase64(data!.path);
}
});
}
@override
void onReady() {
super.onReady();
getUserProfile();
selectedProvince.listen((p0) => getCites());
userProfile.listen((data) {
nameController.text = data.data?.firstName ?? '';
lastNameController.text = data.data?.lastName ?? '';
nationalCodeController.text = data.data?.nationalCode ?? '';
nationalIdController.text = data.data?.nationalId ?? '';
birthdayController.text = data.data?.birthday?.toJalali.formatCompactDate() ?? '';
birthDate.value = data.data?.birthday?.toJalali;
selectedProvince.value = IranProvinceCityModel(
name: data.data?.province ?? '',
id: data.data?.provinceNumber ?? 0,
);
selectedCity.value = IranProvinceCityModel(
name: data.data?.city ?? '',
id: data.data?.cityNumber ?? 0,
);
});
}
@override
void onClose() {
super.onClose();
}
Future<void> getUserProfile() async {
userProfile.value = Resource.loading();
await safeCall<UserProfile?>(
call: () async => await rootLogic.chickenRepository.getUserProfile(
token: rootLogic.tokenService.accessToken.value!,
),
onSuccess: (result) {
if (result != null) {
userProfile.value = Resource.success(result);
}
},
onError: (error, stackTrace) {},
);
}
Future<void> getCites() async {
await safeCall(
call: () =>
rootLogic.chickenRepository.getCity(provinceName: selectedProvince.value?.name ?? ''),
onSuccess: (result) {
if (result != null && result.isNotEmpty) {
cites.value = result;
}
},
);
}
Future<void> updateUserProfile() async {
UserProfile userProfile = UserProfile(
firstName: nameController.text,
lastName: lastNameController.text,
nationalCode: nationalCodeController.text,
nationalId: nationalIdController.text,
birthday: birthDate.value?.toDateTime().formattedDashedGregorian.toString(),
image: _base64Image.value,
personType: 'self',
type: 'self_profile',
);
isOnLoading.value = true;
await safeCall(
call: () async => await rootLogic.chickenRepository.updateUserProfile(
token: rootLogic.tokenService.accessToken.value!,
userProfile: userProfile,
),
onSuccess: (result) {
isOnLoading.value = false;
},
onError: (error, stackTrace) {
isOnLoading.value = false;
},
);
}
Future<void> updatePassword() async {
if (formKey.currentState?.validate() ?? false) {
ChangePasswordRequestModel model = ChangePasswordRequestModel(
username: userProfile.value.data?.mobile,
password: newPasswordController.text,
);
await safeCall(
call: () async => await rootLogic.chickenRepository.updatePassword(
token: rootLogic.tokenService.accessToken.value!,
model: model,
),
);
}
}
void clearPasswordForm() {
oldPasswordController.clear();
newPasswordController.clear();
retryNewPasswordController.clear();
}
}

View File

@@ -0,0 +1,638 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/chicken.dart';
import 'package:rasadyar_chicken/data/di/chicken_di.dart';
import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart';
import 'package:rasadyar_chicken/data/models/response/user_profile/user_profile.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class ProfilePage extends GetView<ProfileLogic> {
const ProfilePage({super.key});
@override
Widget build(BuildContext context) {
return Column(
spacing: 30,
children: [
Expanded(
flex: 1,
child: Container(
color: AppColor.blueNormal,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(),
ObxValue((data) {
final status = data.value.status;
if (status == ResourceStatus.loading) {
return Container(
width: 128.w,
height: 128.h,
child: Center(child: CupertinoActivityIndicator(color: AppColor.greenNormal)),
);
}
if (status == ResourceStatus.error) {
return Container(
width: 128.w,
height: 128.h,
child: Center(child: Text('خطا در دریافت اطلاعات')),
);
}
// Default UI
return Container(
width: 128.w,
height: 128.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColor.blueLightActive,
),
child: Center(
child: CircleAvatar(
radius: 64.w,
backgroundImage: NetworkImage(data.value.data!.image!),
),
),
);
}, controller.userProfile),
],
),
),
),
Expanded(
flex: 3,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 16,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 10),
child: userProfileInformation(),
),
),
Center(
child: Wrap(
alignment: WrapAlignment.center,
spacing: 20,
runSpacing: 10,
children: [
cardActionWidget(
title: 'تغییر رمز عبور',
selected: true,
onPressed: () {
Get.bottomSheet(changePasswordBottomSheet(), isScrollControlled: true);
},
icon: Assets.vec.lockSvg.path,
),
cardActionWidget(
title: 'خروج',
selected: true,
color: ColorFilter.mode(Colors.redAccent, BlendMode.srcIn),
cardColor: Color(0xFFEFEFEF),
textColor: AppColor.redDarkerText,
onPressed: () {
Get.bottomSheet(exitBottomSheet(), isScrollControlled: true);
},
icon: Assets.vec.logoutSvg.path,
),
],
),
),
SizedBox(height: 100),
],
),
),
],
);
}
Container invoiceIssuanceInformation() => Container();
Widget bankInformationWidget() => Column(
spacing: 16,
children: [
itemList(title: 'نام بانک', content: 'سامان'),
itemList(title: 'نام صاحب حساب', content: 'رضا رضایی'),
itemList(title: 'شماره کارت ', content: '54154545415'),
itemList(title: 'شماره حساب', content: '62565263263652'),
itemList(title: 'شماره شبا', content: '62565263263652'),
],
);
Widget userProfileInformation() {
return ObxValue((data) {
if (data.value.status == ResourceStatus.loading) {
return LoadingWidget();
} else if (data.value.status == ResourceStatus.error) {
return ErrorWidget('خطا در دریافت اطلاعات کاربر');
} else if (data.value.status == ResourceStatus.success) {
UserProfile item = data.value.data!;
return Column(
spacing: 6,
children: [
buildRowOnTapped(
onTap: () {
Get.bottomSheet(
userInformationBottomSheet(),
isScrollControlled: true,
ignoreSafeArea: false,
);
},
titleWidget: Column(
spacing: 3,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'اطلاعات هویتی',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
Container(width: 37.w, height: 1.h, color: AppColor.greenNormal),
],
),
valueWidget: Assets.vec.editSvg.svg(
width: 24.w,
height: 24.h,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
itemList(
title: 'نام و نام خانوادگی',
content: item.fullname ?? 'نامشخص',
icon: Assets.vec.userSvg.path,
hasColoredBox: true,
),
itemList(
title: 'موبایل',
content: item.mobile ?? 'نامشخص',
icon: Assets.vec.callSvg.path,
),
itemList(
title: 'کدملی',
content: item.nationalId ?? 'نامشخص',
icon: Assets.vec.tagUserSvg.path,
),
itemList(
title: 'شماره شناسنامه',
content: item.nationalCode ?? 'نامشخص',
icon: Assets.vec.userSquareSvg.path,
),
itemList(
title: 'تاریخ تولد',
content: item.birthday?.toJalali.formatCompactDate() ?? 'نامشخص',
icon: Assets.vec.calendarSvg.path,
),
itemList(
title: 'استان',
content: item.province ?? 'نامشخص',
icon: Assets.vec.pictureFrameSvg.path,
),
itemList(title: 'شهر', content: item.city ?? 'نامشخص', icon: Assets.vec.mapSvg.path),
],
);
} else {
return SizedBox.shrink();
}
}, controller.userProfile);
}
Widget itemList({
required String title,
required String content,
String? icon,
bool hasColoredBox = false,
}) => Container(
padding: EdgeInsets.symmetric(horizontal: 12.h, vertical: 6.h),
decoration: BoxDecoration(
color: hasColoredBox ? AppColor.greenLight : Colors.transparent,
borderRadius: BorderRadius.circular(8),
border: hasColoredBox
? Border.all(width: 0.25, color: AppColor.bgDark)
: Border.all(width: 0, color: Colors.transparent),
),
child: Row(
spacing: 4,
children: [
if (icon != null)
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: SvgGenImage.vec(icon).svg(
width: 20.w,
height: 20.h,
colorFilter: ColorFilter.mode(AppColor.mediumGreyNormalActive, BlendMode.srcIn),
),
),
Text(title, style: AppFonts.yekan12.copyWith(color: AppColor.mediumGreyNormalActive)),
Spacer(),
Text(content, style: AppFonts.yekan13.copyWith(color: AppColor.mediumGreyNormalHover)),
],
),
);
Widget cardActionWidget({
required String title,
required VoidCallback onPressed,
required String icon,
bool selected = false,
ColorFilter? color,
Color? cardColor,
Color? textColor,
}) {
return GestureDetector(
onTap: onPressed,
child: Column(
spacing: 4,
children: [
Container(
width: 52,
height: 52,
padding: EdgeInsets.all(8),
decoration: ShapeDecoration(
color: cardColor ?? AppColor.blueLight,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: SvgGenImage.vec(icon).svg(
width: 40,
height: 40,
colorFilter:
color ??
ColorFilter.mode(
selected ? AppColor.blueNormal : AppColor.whiteLight,
BlendMode.srcIn,
),
),
),
SizedBox(height: 2),
Text(
title,
style: AppFonts.yekan10.copyWith(
color: textColor ?? (selected ? AppColor.blueNormal : AppColor.blueLightActive),
),
textAlign: TextAlign.center,
),
],
),
);
}
Widget userInformationBottomSheet() {
return BaseBottomSheet(
height: 750.h,
child: SingleChildScrollView(
child: Column(
spacing: 8,
children: [
Text(
'ویرایش اطلاعات هویتی',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.darkGreyDarkHover),
),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 12,
children: [
RTextField(
controller: controller.nameController,
label: 'نام',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
RTextField(
controller: controller.lastNameController,
label: 'نام خانوادگی',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
RTextField(
controller: controller.nationalCodeController,
label: 'شماره شناسنامه',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
RTextField(
controller: controller.nationalIdController,
label: 'کد ملی',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
ObxValue((data) {
return RTextField(
controller: controller.birthdayController,
label: 'تاریخ تولد',
initText: data.value?.formatCompactDate() ?? '',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
onTap: () {},
);
}, controller.birthDate),
SizedBox(),
],
),
),
SizedBox(),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 8,
children: [
Text(
'عکس پروفایل',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
ObxValue((data) {
return Container(
width: Get.width,
height: 270,
decoration: BoxDecoration(
color: AppColor.lightGreyNormal,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 1, color: AppColor.blackLight),
),
child: Center(
child: data.value == null
? Padding(
padding: const EdgeInsets.fromLTRB(30, 10, 10, 30),
child: Image.network(
controller.userProfile.value.data?.image ?? '',
),
)
: Image.file(File(data.value!.path), fit: BoxFit.cover),
),
);
}, controller.selectedImage),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
RElevated(
text: 'گالری',
width: 150.w,
height: 40.h,
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
onPressed: () async {
controller.selectedImage.value = await controller.imagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 60,
maxWidth: 1080,
maxHeight: 720,
);
},
),
SizedBox(width: 16),
ROutlinedElevated(
text: 'دوربین',
width: 150.w,
height: 40.h,
textStyle: AppFonts.yekan20.copyWith(color: AppColor.blueNormal),
onPressed: () async {
controller.selectedImage.value = await controller.imagePicker.pickImage(
source: ImageSource.camera,
imageQuality: 60,
maxWidth: 1080,
maxHeight: 720,
);
},
),
],
),
],
),
),
Row(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ObxValue((data) {
return RElevated(
height: 40.h,
text: 'ویرایش',
isLoading: data.value,
onPressed: () async {
await controller.updateUserProfile();
controller.getUserProfile();
Get.back();
},
);
}, controller.isOnLoading),
ROutlinedElevated(
height: 40.h,
text: 'انصراف',
borderColor: AppColor.blueNormal,
onPressed: () {
Get.back();
},
),
],
),
],
),
),
);
}
Widget _provinceWidget() {
return Obx(() {
return OverlayDropdownWidget<IranProvinceCityModel>(
items: controller.rootLogic.provinces,
onChanged: (value) {
controller.selectedProvince.value = value;
},
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;
},
selectedItem: controller.selectedCity.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Text(item?.name ?? 'انتخاب شهر'),
);
}, controller.cites);
}
Widget changePasswordBottomSheet() {
return BaseBottomSheet(
height: 400.h,
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 8,
children: [
Text(
'تغییر رمز عبور',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.darkGreyDarkHover),
),
SizedBox(),
RTextField(
controller: controller.oldPasswordController,
hintText: 'رمز عبور قبلی',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'رمز عبور را وارد کنید';
} else if (controller.userProfile.value.data?.password != value) {
return 'رمز عبور صحیح نیست';
}
return null;
},
),
RTextField(
controller: controller.newPasswordController,
hintText: 'رمز عبور جدید',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'رمز عبور را وارد کنید';
} else if (value.length < 6) {
return 'رمز عبور باید بیش از 6 کارکتر باشد.';
}
return null;
},
),
RTextField(
controller: controller.retryNewPasswordController,
hintText: 'تکرار رمز عبور جدید',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'رمز عبور را وارد کنید';
} else if (value.length < 6) {
return 'رمز عبور باید بیش از 6 کارکتر باشد.';
} else if (controller.newPasswordController.text != value) {
return 'رمز عبور جدید یکسان نیست';
}
return null;
},
),
SizedBox(),
Row(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.center,
children: [
RElevated(
height: 40.h,
text: 'ویرایش',
onPressed: () async {
if (controller.formKey.currentState?.validate() != true) {
return;
}
await controller.updatePassword();
controller.getUserProfile();
controller.clearPasswordForm();
Get.back();
},
),
ROutlinedElevated(
height: 40.h,
text: 'انصراف',
borderColor: AppColor.blueNormal,
onPressed: () {
Get.back();
},
),
],
),
],
),
),
),
);
}
Widget exitBottomSheet() {
return BaseBottomSheet(
height: 220.h,
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 8,
children: [
Text('خروج', style: AppFonts.yekan16Bold.copyWith(color: AppColor.error)),
SizedBox(),
Text(
'آیا مطمئن هستید که می‌خواهید از حساب کاربری خود خارج شوید؟',
textAlign: TextAlign.center,
style: AppFonts.yekan16Bold.copyWith(color: AppColor.textColor),
),
SizedBox(),
Row(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.center,
children: [
RElevated(
height: 40.h,
text: 'خروج',
backgroundColor: AppColor.error,
onPressed: () async {
await controller.rootLogic.tokenService.deleteTokens().then((value){
Get.back();
Get.offAllNamed(ChickenRoutes.auth, arguments: Module.chicken);
});
},
),
ROutlinedElevated(
height: 40.h,
text: 'انصراف',
borderColor: AppColor.blueNormal,
onPressed: () {
Get.back();
},
),
],
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,162 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:rasadyar_chicken/data/data_source/local/chicken_local.dart';
import 'package:rasadyar_chicken/data/data_source/local/chicken_local_imp.dart';
import 'package:rasadyar_chicken/data/di/chicken_di.dart';
import 'package:rasadyar_chicken/data/models/local/widely_used_local_model.dart';
import 'package:rasadyar_chicken/data/models/response/inventory/inventory_model.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/repositories/chicken/chicken_repository.dart';
import 'package:rasadyar_chicken/data/repositories/chicken/chicken_repository_imp.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/buy/view.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/home/view.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/profile/view.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/sale/view.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/segmentation/view.dart';
import 'package:rasadyar_chicken/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
enum ErrorLocationType { serviceDisabled, permissionDenied, none }
class StewardRootLogic extends GetxController {
RxInt currentPage = 2.obs;
List<Widget> pages = [BuyPage(), SalePage(), HomePage(), SegmentationPage(), ProfilePage()];
final defaultRoutes = <int, String>{0: ChickenRoutes.buySteward, 1: ChickenRoutes.saleSteward};
RxList<ProductModel> rolesProductsModel = RxList<ProductModel>();
Rxn<WidelyUsedLocalModel> widelyUsedList = Rxn<WidelyUsedLocalModel>();
late DioRemote dioRemote;
var tokenService = Get.find<TokenStorageService>();
late ChickenRepository chickenRepository;
late ChickenLocalDataSource localDatasource;
RxList<ErrorLocationType> errorLocationType = RxList();
RxMap<int, dynamic> inventoryExpandedList = RxMap();
Rxn<InventoryModel> inventoryModel = Rxn<InventoryModel>();
RxList<IranProvinceCityModel> provinces = <IranProvinceCityModel>[].obs;
// Cancel tokens for API calls
CancelToken? _inventoryCancelToken;
CancelToken? _provincesCancelToken;
@override
void onInit() {
super.onInit();
localDatasource = diChicken.get<ChickenLocalDataSource>();
chickenRepository = diChicken.get<ChickenRepository>();
/*localDatasource.openBox().then((value) async {
widelyUsedList.value = localDatasource.getAllWidely();
});*/
}
@override
void onReady() {
super.onReady();
if (provinces.isEmpty) {
getProvinces();
}
if (inventoryModel.value == null) {
getInventory();
}
if (rolesProductsModel.isEmpty) {
getRolesProducts();
}
if (widelyUsedList.value?.hasInit != true) {
localDatasource.initWidleyUsed().then((value) => localDatasource.getAllWidely());
}
}
@override
void onClose() {
// Cancel any ongoing requests when controller is disposed
_inventoryCancelToken?.cancel();
_provincesCancelToken?.cancel();
super.onClose();
}
void toggleExpanded(int index) {
if (inventoryExpandedList.keys.contains(index)) {
inventoryExpandedList.remove(index);
} else {
inventoryExpandedList[index] = false;
}
}
Future<void> getInventory() async {
// Cancel previous request if still running
_inventoryCancelToken?.cancel();
_inventoryCancelToken = CancelToken();
await safeCall<List<InventoryModel>?>(
call: () async => await chickenRepository.getInventory(
token: tokenService.accessToken.value!,
cancelToken: _inventoryCancelToken,
),
onSuccess: (result) {
if (result != null) {
inventoryModel.value = result.first;
}
},
onError: (error, stackTrace) {
if (error is DioException && error.type == DioExceptionType.cancel) {
// Request was cancelled, ignore the error
return;
}
},
);
}
void rootErrorHandler(DioException error) {
handleGeneric(error, () {
tokenService.deleteTokens();
});
}
void changePage(int index) {
currentPage.value = index;
}
Future<void> getProvinces() async {
// Cancel previous request if still running
_provincesCancelToken?.cancel();
_provincesCancelToken = CancelToken();
try {
final res = await chickenRepository.getProvince(cancelToken: _provincesCancelToken);
if (res != null) {
provinces.clear();
provinces.value = res;
}
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) {
// Request was cancelled, ignore the error
return;
}
provinces.clear();
}
}
Future<void> getRolesProducts() async {
safeCall(
call: () async =>
await chickenRepository.getRolesProducts(token: tokenService.accessToken.value!),
onSuccess: (result) {
if (result != null) {
rolesProductsModel.value = result;
}
},
onError: (error, stacktrace) {},
);
}
}

View File

@@ -0,0 +1,642 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rasadyar_chicken/chicken.dart';
import 'package:rasadyar_chicken/data/models/response/kill_house_distribution_info/kill_house_distribution_info.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class StewardRootPage extends GetView<StewardRootLogic> {
StewardRootPage({super.key});
DateTime? _lastBackPressed;
@override
Widget build(BuildContext context) {
return ObxValue((data) {
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
final nestedKey = Get.nestedKey(controller.currentPage.value);
final currentNavigator = nestedKey?.currentState;
if (currentNavigator?.canPop() ?? false) {
currentNavigator?.pop();
} else {
final now = DateTime.now();
if (_lastBackPressed == null ||
now.difference(_lastBackPressed!) > Duration(seconds: 2)) {
_lastBackPressed = now;
Get.snackbar(
'خروج از برنامه',
'برای خروج دوباره بازگشت را بزنید',
snackPosition: SnackPosition.TOP,
duration: Duration(seconds: 2),
backgroundColor: AppColor.warning,
);
} else {
await SystemNavigator.pop();
}
}
},
child: Scaffold(
backgroundColor: AppColor.bgLight,
body: IndexedStack(
children: [
Navigator(
key: Get.nestedKey(0),
onGenerateRoute: (settings) {
final page = ChickenPages.pages.firstWhere(
(e) => e.name == settings.name,
orElse: () => ChickenPages.pages.firstWhere((e) => e.name == ChickenRoutes.buySteward),
);
return buildRouteFromGetPage(page);
},
),
Navigator(
key: Get.nestedKey(1),
onGenerateRoute: (settings) {
final page = ChickenPages.pages.firstWhere(
(e) => e.name == settings.name,
orElse: () =>
ChickenPages.pages.firstWhere((e) => e.name == ChickenRoutes.saleSteward),
);
return buildRouteFromGetPage(page);
},
),
Navigator(
key: Get.nestedKey(2),
onGenerateRoute: (settings) => GetPageRoute(page: () => controller.pages[2]),
),
Navigator(
key: Get.nestedKey(3),
onGenerateRoute: (settings) => GetPageRoute(page: () => controller.pages[3]),
),
Navigator(
key: Get.nestedKey(4),
onGenerateRoute: (settings) => GetPageRoute(page: () => controller.pages[4]),
),
],
index: data.value,
),
bottomNavigationBar: RBottomNavigation(
items: [
RBottomNavigationItem(
label: 'خرید',
icon: Assets.vec.buySvg.path,
isSelected: controller.currentPage.value == 0,
onTap: () {
Get.nestedKey(1)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(0);
},
),
RBottomNavigationItem(
label: 'فروش',
icon: Assets.vec.saleSvg.path,
isSelected: controller.currentPage.value == 1,
onTap: () {
Get.nestedKey(0)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(1);
},
),
RBottomNavigationItem(
label: 'خانه',
icon: Assets.vec.homeSvg.path,
isSelected: controller.currentPage.value == 2,
onTap: () {
Get.nestedKey(1)?.currentState?.popUntil((route) => route.isFirst);
Get.nestedKey(0)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(2);
},
),
RBottomNavigationItem(
label: 'قطعه بندی',
icon: Assets.vec.convertCubeSvg.path,
isSelected: controller.currentPage.value == 3,
onTap: () {
Get.nestedKey(1)?.currentState?.popUntil((route) => route.isFirst);
Get.nestedKey(0)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(3);
},
),
RBottomNavigationItem(
label: 'پروفایل',
icon: Assets.vec.profileCircleSvg.path,
isSelected: controller.currentPage.value == 4,
onTap: () {
Get.nestedKey(1)?.currentState?.popUntil((route) => route.isFirst);
Get.nestedKey(0)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(4);
},
),
],
),
),
);
}, controller.currentPage);
}
Container _todayShipmentWidget() {
return Container(
height: 70,
width: Get.width / 2,
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
clipBehavior: Clip.hardEdge,
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [const Color(0xFFEAEFFF), Colors.white],
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Assets.icons.cubeScan.svg(width: 30.w, height: 30),
Text(
'بارهای امروز',
textAlign: TextAlign.right,
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
],
),
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
'2،225،256',
textAlign: TextAlign.right,
style: AppFonts.yekan16.copyWith(color: AppColor.textColor),
),
Text(
'کیلوگرم',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.textColor),
),
],
),
),
],
),
);
}
Container _informationLabelCard({
required String title,
required String description,
String unit = 'کیلوگرم',
required String iconPath,
required Color iconColor,
required Color bgDescriptionColor,
required Color bgLabelColor,
}) {
return Container(
height: 82,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
clipBehavior: Clip.hardEdge,
child: Row(
children: [
// Left side with icon and title
Expanded(
child: Container(
height: 82,
decoration: BoxDecoration(
color: bgLabelColor,
borderRadius: BorderRadius.only(
topRight: Radius.circular(8),
bottomRight: Radius.circular(8),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
SvgGenImage.vec(iconPath).svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn),
),
Text(
title,
textAlign: TextAlign.right,
style: AppFonts.yekan14.copyWith(color: AppColor.mediumGreyDarkActive),
),
],
),
),
),
// Right side with description and unit
Expanded(
child: Container(
decoration: BoxDecoration(
color: bgDescriptionColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
bottomLeft: Radius.circular(8),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
description,
textAlign: TextAlign.right,
style: AppFonts.yekan16.copyWith(color: AppColor.mediumGreyDarkActive),
),
Text(
unit,
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.mediumGreyDarkActive),
),
],
),
),
),
],
),
);
}
Container _informationIconCard({
required String title,
required String description,
String unit = 'کیلوگرم',
required String iconPath,
required Color iconColor,
required Color bgDescriptionColor,
required Color bgLabelColor,
}) {
return Container(
height: 110,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
clipBehavior: Clip.hardEdge,
child: Stack(
alignment: Alignment.topCenter,
children: [
Positioned(
bottom: 0,
right: 0,
left: 0,
child: Container(
height: 91,
decoration: BoxDecoration(
color: bgDescriptionColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 0.25, color: const Color(0xFFB4B4B4)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
title,
textAlign: TextAlign.right,
style: AppFonts.yekan14.copyWith(color: AppColor.mediumGreyDarkActive),
),
Text(
description,
textAlign: TextAlign.right,
style: AppFonts.yekan16.copyWith(color: AppColor.mediumGreyDarkActive),
),
Text(
unit,
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.mediumGreyDarkActive),
),
],
),
),
),
Positioned(
top: 0,
child: Container(
width: 32,
height: 32,
decoration: ShapeDecoration(
color: bgLabelColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
side: BorderSide(width: 0.25, color: const Color(0xFFD5D5D5)),
),
),
child: Center(
child: SvgGenImage.vec(iconPath).svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn),
),
),
),
),
],
),
);
}
Widget widelyUsed({
required String title,
required String iconPath,
required VoidCallback onTap,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
spacing: 4,
children: [
Container(
width: 48,
height: 48,
padding: EdgeInsets.all(4),
decoration: ShapeDecoration(
color: const Color(0xFFBECDFF),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: Container(
width: 40,
height: 40,
decoration: ShapeDecoration(
color: AppColor.blueNormal,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: SvgGenImage.vec(iconPath).svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
fit: BoxFit.cover,
),
),
),
Text(title, style: AppFonts.yekan10.copyWith(color: AppColor.blueNormal)),
],
);
}
Widget addWidelyUsed({required VoidCallback onTap}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
spacing: 4,
children: [
Container(
width: 48,
height: 48,
padding: EdgeInsets.all(4),
decoration: ShapeDecoration(
color: const Color(0xFFD9F7F0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: Assets.vec.messageAddSvg.svg(
width: 40,
height: 40,
colorFilter: ColorFilter.mode(AppColor.greenNormal, BlendMode.srcIn),
fit: BoxFit.cover,
),
),
Text('افزودن', style: AppFonts.yekan10.copyWith(color: AppColor.greenDarkHover)),
],
);
}
/*Column oldPage() {
return Column(
children: [
inventoryWidget(),
ObxValue((data) => broadcastInformationWidget(data.value), controller.killHouseDistributionInfo),
SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
cardWidget(
title: 'ورود به انبار',
iconPath: Assets.icons.whareHouse.path,
onTap: () {
Get.toNamed(ChickenRoutes.enteringTheWarehouse);
},
),
cardWidget(
title: 'فروش داخل استان',
iconPath: Assets.icons.inside.path,
onTap: () {
Get.toNamed(ChickenRoutes.salesInProvince);
},
),
cardWidget(
title: 'فروش خارج استان',
iconPath: Assets.icons.outside.path,
onTap: () {
Get.toNamed(ChickenRoutes.salesOutOfProvince);
},
),
],
),
),
],
);
}
Widget inventoryWidget() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
children: [
const SizedBox(height: 20),
Align(
alignment: Alignment.centerRight,
child: Text('موجودی انبار', style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal)),
),
SizedBox(height: 4),
ObxValue(
(data) =>
data.isEmpty
? Container(
margin: const EdgeInsets.symmetric(vertical: 2),
height: 80,
padding: EdgeInsets.all(6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.blueNormal, width: 1),
),
child: Center(child: CircularProgressIndicator()),
)
: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: controller.inventoryList.length,
separatorBuilder: (context, index) => const SizedBox(height: 8),
itemBuilder: (context, index) {
return ObxValue((expand) {
return GestureDetector(
onTap: () {
controller.toggleExpanded(index);
},
behavior: HitTestBehavior.opaque,
child: AnimatedContainer(
onEnd: () {
controller.inventoryExpandedList[index] = !controller.inventoryExpandedList[index]!;
},
margin: const EdgeInsets.symmetric(vertical: 2),
padding: EdgeInsets.all(6),
curve: Curves.easeInOut,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.blueNormal, width: 1),
),
duration: const Duration(seconds: 1),
height: expand.keys.contains(index) ? 250 : 80,
child: inventoryItem(
isExpanded: expand.keys.contains(index) && expand[index]!,
index: index,
model: controller.inventoryList[index],
),
),
);
}, controller.inventoryExpandedList);
},
),
controller.inventoryList,
),
],
),
);
}
Widget inventoryItem({required bool isExpanded, required int index, required InventoryModel model}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 8,
children: [
buildRow('نام محصول', model.name ?? ''),
Visibility(
visible: isExpanded,
child: Column(
spacing: 8,
children: [
buildRow('وزن خریدهای دولتی داخل استان (کیلوگرم)', '0326598653'),
buildRow('وزن خریدهای آزاد داخل استان (کیلوگرم)', model.receiveFreeCarcassesWeight.toString()),
buildRow('وزن خریدهای خارج استان (کیلوگرم)', model.freeBuyingCarcassesWeight.toString()),
buildRow('کل ورودی به انبار (کیلوگرم)', model.totalFreeBarsCarcassesWeight.toString()),
buildRow('کل فروش (کیلوگرم)', model.realAllocatedWeight.toString()),
buildRow('مانده انبار (کیلوگرم)', model.totalRemainWeight.toString()),
],
),
),
],
);
}*/
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: 1,
child: Text(
value,
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.darkGreyDarkHover),
),
),
],
),
);
}
Widget broadcastInformationWidget(KillHouseDistributionInfo? model) {
return Container(
height: 140,
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.blueNormal, width: 1),
),
child: model != null
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 10,
children: [
Text(
'اطلاعات ارسالی',
textAlign: TextAlign.right,
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
const SizedBox(height: 12),
buildRow(
'فروش و توزیع داخل استان (کیلوگرم)',
model.stewardAllocationsWeight!.toInt().toString(),
),
buildRow(
'فروش و توزیع خارج استان (کیلوگرم)',
model.freeSalesWeight!.toInt().toString(),
),
],
)
: const Center(child: CircularProgressIndicator()),
);
}
Widget cardWidget({
required String title,
required String iconPath,
required VoidCallback onTap,
}) {
return Container(
width: Get.width / 4,
height: 130,
child: GestureDetector(
onTap: onTap,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(width: 1, color: AppColor.blueNormal),
),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SvgGenImage(iconPath).svg(width: 50, height: 50),
SizedBox(height: 4),
Text(
title,
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,117 @@
import 'package:rasadyar_chicken/data/models/request/conform_allocation/conform_allocation.dart';
import 'package:rasadyar_chicken/data/models/response/allocated_made/allocated_made.dart';
import 'package:rasadyar_chicken/data/models/response/guild/guild_model.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/steward_free_bar_dashboard/steward_free_bar_dashboard.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class SaleLogic extends GetxController {
Rxn<List<AllocatedMadeModel>?> allocatedMadeModel = Rxn<List<AllocatedMadeModel>?>();
RxList<GuildModel> guildsModel = <GuildModel>[].obs;
Rxn<StewardFreeBarDashboard> stewardFreeDashboard = Rxn<StewardFreeBarDashboard>();
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
List<String> routesName = ['فروش'];
@override
void onReady() {
super.onReady();
getStewardDashBord();
}
Future<void> getAllocatedMade() async {
safeCall(
call: () async => await rootLogic.chickenRepository.getAllocatedMade(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(page: 1, pageSize: 20, search: 'filter', role: 'Steward'),
),
onSuccess: (result) {
if (result != null) {
allocatedMadeModel.value = result.results;
}
},
onError: (error, stacktrace) {},
);
}
void checkVerfication() {}
void confirmAllocation(ConformAllocation allocation) {
safeCall(
call: () async => await rootLogic.chickenRepository.confirmAllocation(
token: rootLogic.tokenService.accessToken.value!,
allocation: allocation.toJson(),
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stacktrace) {},
);
}
void denyAllocation(String token) {
safeCall(
call: () async => await rootLogic.chickenRepository.denyAllocation(
token: rootLogic.tokenService.accessToken.value!,
allocationToken: token,
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stacktrace) {},
);
}
Future<void> confirmAllAllocations() async {
safeCall(
call: () async => await rootLogic.chickenRepository.confirmAllAllocation(
token: rootLogic.tokenService.accessToken.value!,
allocationTokens: allocatedMadeModel.value?.map((e) => e.key!).toList() ?? [],
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stacktrace) {},
);
}
Future<void> getGuilds() async {}
Future<void> addSale() async {}
void setSelectedGuild(GuildModel value) {}
void setSelectedProduct(ProductModel value) {}
Future<void> getStewardDashBord() async {
safeCall(
call: () async => await rootLogic.chickenRepository.getStewardDashboard(
token: rootLogic.tokenService.accessToken.value!,
stratDate: DateTime.now().formattedDashedGregorian,
endDate: DateTime.now().formattedDashedGregorian,
),
onSuccess: (result) {
if (result != null) {
stewardFreeDashboard.value = result;
}
},
onError: (error, stacktrace) {},
);
}
Future<void> submitAllocation() async {}
@override
void dispose() {
rootLogic.inventoryExpandedList.clear();
super.dispose();
}
}

View File

@@ -0,0 +1,186 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/chicken.dart';
import 'package:rasadyar_chicken/data/models/response/steward_free_bar_dashboard/steward_free_bar_dashboard.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/sale_buy_card_item.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class SalePage extends GetView<SaleLogic> {
SalePage({super.key});
@override
Widget build(BuildContext context) {
return BasePage(
routes: controller.routesName,
isBase: true,
widgets: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Wrap(
alignment: WrapAlignment.center,
spacing: 14.w,
children: [
saleOrBuyItemCard(
title: 'فروش داخل استان',
iconPath: Assets.vec.cubeSvg.path,
onTap: () {
Get.toNamed(ChickenRoutes.salesInProvinceSteward, id: 1);
},
),
saleOrBuyItemCard(
title: 'فروش خارج استان',
iconPath: Assets.vec.truckFastSvg.path,
onTap: () {
Get.toNamed(ChickenRoutes.salesOutOfProvinceSteward, id: 1);
},
),
],
),
],
),
],
);
}
Widget addSaleOutOfTheProvinceBottomSheet() {
return BaseBottomSheet(
child: Column(
children: [
const SizedBox(height: 20),
Align(
alignment: Alignment.centerRight,
child: Text(
'ثبت فروش خارج استان',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
),
SizedBox(height: 4),
RElevated(text: 'ثبت توزیع/ فروش', onPressed: () {}),
],
),
);
}
Widget _typeOuterInfoCard({
required String title,
required String iconPath,
required Color foregroundColor,
VoidCallback? onTap,
}) {
return InkWell(
onTap: onTap,
child: Container(
height: 180,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 1, color: foregroundColor),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Positioned(
top: -41,
child: SvgGenImage.vec(iconPath).svg(
width: 45,
height: 45,
colorFilter: ColorFilter.mode(foregroundColor, BlendMode.srcIn),
),
),
Assets.vec.shoppingBasketSvg.svg(
width: 55,
height: 60,
colorFilter: ColorFilter.mode(foregroundColor, BlendMode.srcIn),
fit: BoxFit.cover,
),
],
),
const SizedBox(height: 15),
Text(
title,
textAlign: TextAlign.right,
style: AppFonts.yekan16Bold.copyWith(color: foregroundColor),
),
],
),
),
);
}
Widget summaryOfInformation(StewardFreeBarDashboard? model) {
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
children: [
Text(
'خلاصه اطلاعات',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
],
),
),
Container(
height: 140,
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.blueNormal, width: 1),
),
child: model == null
? const Center(child: CircularProgressIndicator())
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 10,
children: [
const SizedBox(height: 12),
buildRow('تعداد کل بارها', model.totalQuantity?.toString() ?? '0'),
buildRow('تعداد کل', model.totalBars?.toString() ?? '0'),
buildRow('وزن کل (کیلوگرم)', model.totalWeight?.toString() ?? '0'),
],
),
),
],
);
}
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),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,419 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/request/conform_allocation/conform_allocation.dart';
import 'package:rasadyar_chicken/data/models/request/submit_steward_allocation/submit_steward_allocation.dart';
import 'package:rasadyar_chicken/data/models/response/allocated_made/allocated_made.dart';
import 'package:rasadyar_chicken/data/models/response/guild/guild_model.dart';
import 'package:rasadyar_chicken/data/models/response/guild_profile/guild_profile.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/root/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/sale/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/string_utils.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
class SalesInProvinceLogic extends GetxController {
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
SaleLogic saleLogic = Get.find<SaleLogic>();
RxnString searchedValue = RxnString();
RxList<int> isExpandedList = <int>[].obs;
RxList<String> routesName = RxList();
Rx<Color> bgConfirmAllColor = AppColor.blueNormal.obs;
final RxBool isLoadingMoreAllocationsMade = false.obs;
Timer? _flashingTimer;
Rx<Resource<PaginationModel<AllocatedMadeModel>>> allocatedList =
Resource<PaginationModel<AllocatedMadeModel>>.loading().obs;
RxList<ProductModel> rolesProductsModel = RxList<ProductModel>();
RxList<GuildModel> guildsModel = <GuildModel>[].obs;
GlobalKey<FormState> formKey = GlobalKey<FormState>();
Rxn<Jalali> fromDateFilter = Rxn<Jalali>(null);
Rxn<Jalali> toDateFilter = Rxn<Jalali>(null);
Rxn<ProductModel> selectedProductModel = Rxn<ProductModel>();
Rxn<GuildModel> selectedGuildModel = Rxn<GuildModel>();
Rxn<GuildProfile> guildProfile = Rxn<GuildProfile>();
RxInt saleType = 1.obs;
RxInt weight = 0.obs;
RxInt pricePerKilo = 0.obs;
RxInt totalCost = 0.obs;
RxBool isValid = false.obs;
final weightController = TextEditingController();
final pricePerKiloController = TextEditingController();
final totalCostController = TextEditingController();
final ScrollController scrollControllerAllocationsMade = ScrollController();
final RxInt currentPage = 1.obs;
final RxBool addPageAllocationsMade = false.obs;
final RxBool hasMoreDataAllocationsMade = true.obs;
Rxn<AllocatedMadeModel> selectedAllocationModelForUpdate = Rxn<AllocatedMadeModel>();
SubmitStewardAllocation? tmpStewardAllocation;
@override
void onInit() {
super.onInit();
routesName.value = [...saleLogic.routesName, 'داخل استان'].toList();
getAllocatedMade();
getRolesProducts();
getGuilds();
getGuildProfile();
ever(saleType, (callback) {
getGuilds();
});
debounce(weight, time: Duration(milliseconds: 110), (callback) {
totalCost.value = callback * weight.value;
});
debounce(pricePerKilo, time: Duration(milliseconds: 100), (callback) {
totalCost.value = callback * weight.value;
});
totalCost.listen((data) {
totalCostController.text = data
.toString()
.separatedByComma;
isValid.value =
weight.value > 0 &&
pricePerKilo.value > 0 &&
totalCost.value > 0 &&
selectedProductModel.value != null &&
selectedGuildModel.value != null;
});
everAll([
totalCost,
weight,
pricePerKilo,
totalCost,
selectedProductModel,
selectedGuildModel
], (callback) => checkVerification(),);
scrollControllerAllocationsMade.addListener(() {
if (scrollControllerAllocationsMade.position.pixels >=
scrollControllerAllocationsMade.position.maxScrollExtent - 100) {
addPageAllocationsMade.value = true;
getAllocatedMade();
}
});
debounce(
searchedValue,
(callback) => getAllocatedMade(),
time: Duration(milliseconds: timeDebounce),
);
}
Future<void> getAllocatedMade([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
allocatedList.value = Resource<PaginationModel<AllocatedMadeModel>>.loading();
}
if (searchedValue.value != null &&
searchedValue.value!.trim().isNotEmpty &&
currentPage.value > 1) {
currentPage.value = 1; // Reset to first page if search value is set
}
safeCall(
call: () async =>
await rootLogic.chickenRepository.getAllocatedMade(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
page: currentPage.value,
pageSize: 20,
search: 'filter',
role: 'Steward',
value: searchedValue.value,
),
),
onSuccess: (res) async {
await Future.delayed(Duration(milliseconds: 200));
if ((res?.count ?? 0) == 0) {
allocatedList.value = Resource<PaginationModel<AllocatedMadeModel>>.empty();
} else {
allocatedList.value = Resource<PaginationModel<AllocatedMadeModel>>.success(
PaginationModel<AllocatedMadeModel>(
count: res?.count ?? 0,
next: res?.next,
previous: res?.previous,
results: isLoadingMore
? [...(allocatedList.value.data?.results ?? []), ...(res?.results ?? [])]
: res?.results ?? [],
),
);
isLoadingMoreAllocationsMade.value = false;
if ((allocatedList.value.data?.results?.length ?? 0) > 1) {
flashingFabBgColor();
}
}
},
onError: (error, stacktrace) {
isLoadingMoreAllocationsMade.value = false;
},
);
}
void checkVerification() {
isValid.value =
weight.value > 0 &&
pricePerKilo.value > 0 &&
totalCost.value > 0 &&
selectedProductModel.value != null &&
selectedGuildModel.value != null;
}
void confirmAllocation(ConformAllocation allocation) {
safeCall(
call: () async =>
await rootLogic.chickenRepository.confirmAllocation(
token: rootLogic.tokenService.accessToken.value!,
allocation: allocation.toJson(),
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stacktrace) {},
);
}
void denyAllocation(String token) {
safeCall(
call: () async =>
await rootLogic.chickenRepository.denyAllocation(
token: rootLogic.tokenService.accessToken.value!,
allocationToken: token,
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stacktrace) {},
);
}
Future<void> confirmAllAllocations() async {
safeCall(
call: () async =>
await rootLogic.chickenRepository.confirmAllAllocation(
token: rootLogic.tokenService.accessToken.value!,
allocationTokens: allocatedList.value.data?.results?.map((e) => e.key!).toList() ?? [],
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stacktrace) {},
);
}
Future<void> getRolesProducts() async {
safeCall(
call: () async =>
await rootLogic.chickenRepository.getRolesProducts(
token: rootLogic.tokenService.accessToken.value!,
),
onSuccess: (result) {
if (result != null) {
rolesProductsModel.value = result;
selectedProductModel.value = result.first;
}
},
onError: (error, stacktrace) {},
);
}
Future<void> getGuilds() async {
safeCall(
call: () async =>
await rootLogic.chickenRepository.getGuilds(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
queryParams: {'free': saleType.value == 2 ? true : false},
role: 'Steward',
),
),
onSuccess: (result) {
if (result != null) {
guildsModel.clear();
guildsModel.addAll(result);
}
},
onError: (error, stacktrace) {},
);
}
Future<void> addSale() async {}
void setSelectedGuild(GuildModel value) {
selectedGuildModel.value = value;
update();
}
void setSelectedProduct(ProductModel value) {
selectedProductModel.value = value;
update();
}
Future<void> getGuildProfile() async {
await safeCall(
call: () async =>
await rootLogic.chickenRepository.getProfile(
token: rootLogic.tokenService.accessToken.value!,
),
onError: (error, stackTrace) {},
onSuccess: (result) {
guildProfile.value = result;
},
);
}
void setSubmitData() {
tmpStewardAllocation = SubmitStewardAllocation(
approvedPriceStatus: false,
allocationType:
'${guildProfile.value?.steward == true ? "steward" : "guild"}_${selectedGuildModel.value
?.steward == true ? "steward" : "guild"}',
sellerType: guildProfile.value?.steward == true ? "Steward" : "Guild",
buyerType: selectedGuildModel.value?.steward == true ? "Steward" : "Guild",
amount: pricePerKilo.value,
totalAmount: totalCost.value,
weightOfCarcasses: weight.value,
sellType: saleType.value == 2 ? "free" : 'exclusive',
numberOfCarcasses: 0,
guildKey: selectedGuildModel.value?.key,
productKey: selectedProductModel.value?.key,
date: DateTime
.now()
.formattedDashedGregorian,
type: "manual",
);
}
Future<void> submitAllocation() async {
safeCall(
call: () async =>
await rootLogic.chickenRepository.postSubmitStewardAllocation(
token: rootLogic.tokenService.accessToken.value!,
request: tmpStewardAllocation!,
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stackTrace) {},
);
}
Future<void> deleteAllocation(AllocatedMadeModel model) async {
safeCall(
call: () async =>
await rootLogic.chickenRepository.deleteStewardAllocation(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: {'steward_allocation_key': model.key},
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stackTrace) {},
);
}
@override
void dispose() {
rootLogic.inventoryExpandedList.clear();
stopFlashing();
super.dispose();
}
void setEditData(AllocatedMadeModel item) {
selectedAllocationModelForUpdate.value = item;
selectedProductModel.value = rolesProductsModel.first;
selectedGuildModel.value = GuildModel(guildsName: 'tst');
weight.value = item.weightOfCarcasses ?? 0;
pricePerKilo.value = item.amount ?? 0;
totalCost.value = item.totalAmount ?? 0;
weightController.text = weight.value
.toString()
.separatedByComma;
pricePerKiloController.text = pricePerKilo.value
.toString()
.separatedByComma;
totalCostController.text = totalCost.value
.toString()
.separatedByComma;
isValid.value = true;
}
void clearForm() {
selectedGuildModel.value = null;
weight.value = 0;
pricePerKilo.value = 0;
totalCost.value = 0;
weightController.clear();
pricePerKiloController.clear();
totalCostController.clear();
isValid.value = false;
}
Future<void> updateAllocation() async {
ConformAllocation updatedAllocationModel = ConformAllocation(
allocation_key: selectedAllocationModelForUpdate.value?.key,
amount: pricePerKilo.value,
total_amount: totalCost.value,
number_of_carcasses: 0,
weight_of_carcasses: weight.value,
);
safeCall(
call: () async =>
await rootLogic.chickenRepository.updateStewardAllocation(
token: rootLogic.tokenService.accessToken.value!,
request: updatedAllocationModel,
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stackTrace) {},
);
}
void setSearchValue(String? data) {
searchedValue.value = data?.trim();
}
void flashingFabBgColor() {
_flashingTimer?.cancel();
_flashingTimer = Timer.periodic(Duration(seconds: 2), (timer) {
if (bgConfirmAllColor.value == AppColor.blueNormal) {
bgConfirmAllColor.value = AppColor.blueLightHover;
} else {
bgConfirmAllColor.value = AppColor.blueNormal;
}
});
}
void stopFlashing() {
_flashingTimer?.cancel();
_flashingTimer = null;
bgConfirmAllColor.value = AppColor.blueNormal; // بازگرداندن به رنگ پیش‌فرض
}
Steward? getBuyerInformation(AllocatedMadeModel model) {
if (model.allocationType?.buyerIsGuild) {
return model.toGuilds;
} else {
return model.steward;
}
}
}

View File

@@ -0,0 +1,831 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rasadyar_chicken/data/models/response/allocated_made/allocated_made.dart';
import 'package:rasadyar_chicken/data/models/response/guild/guild_model.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/presentation/utils/string_utils.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/steward/inventory_widget.dart';
import 'package:rasadyar_chicken/presentation/widget/page_route.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class SalesInProvincePage extends GetView<SalesInProvinceLogic> {
SalesInProvincePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: BasePage(
routesWidget: ObxValue((route) => buildPageRoute(route), controller.routesName),
onBackPressed: () => Get.back(id: 1),
onSearchChanged: (data) => controller.setSearchValue(data),
filteringWidget: filterBottomSheet(),
widgets: [
inventoryWidget(controller.rootLogic),
Expanded(
child: ObxValue((data) {
return RPaginatedListView(
listType: ListType.separated,
resource: data.value,
hasMore: data.value.data?.next != null,
isPaginating: controller.isLoadingMoreAllocationsMade.value,
onRefresh: () async {
controller.currentPage.value = 1;
await controller.getAllocatedMade();
},
onLoadMore: () async {
controller.currentPage.value++;
iLog(controller.currentPage.value);
await controller.getAllocatedMade(true);
},
padding: EdgeInsets.fromLTRB(8, 8, 8, 80),
itemBuilder: (context, index) {
var item = data.value.data!.results![index];
return ObxValue((val) {
return ExpandableListItem2(
selected: val.contains(index),
onTap: () => controller.isExpandedList.toggle(index),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item, index),
labelColor: AppColor.blueLight,
labelIcon: Assets.vec.timerSvg.path,
labelIconColor: item.registrationCode == null
? AppColor.darkGreyDark
: AppColor.error,
);
}, controller.isExpandedList);
},
itemCount: data.value.data?.results?.length ?? 0,
separatorBuilder: (context, index) => SizedBox(height: 8.h),
);
}, controller.allocatedList),
),
],
),
floatingActionButton: SizedBox(
width: Get.width - 30,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RFab.add(
onPressed: () {
Get.bottomSheet(
addOrEditBottomSheet(),
isScrollControlled: true,
backgroundColor: Colors.transparent,
).whenComplete(() {
controller.clearForm();
});
},
),
ObxValue((data) {
return Visibility(
visible: (data.value.data?.results?.length ?? 0) > 1,
child: AnimatedFab(
onPressed: () async {
Get.defaultDialog(
title: 'تایید یکجا',
middleText: 'آیا از تایید تمامی تخصیص ها اطمینان دارید؟',
confirm: ElevatedButton(
onPressed: () async {
await controller.confirmAllAllocations();
controller.getAllocatedMade();
controller.rootLogic.getInventory();
Get.back();
},
child: Text('تایید'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.blueNormal,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
),
cancel: OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: AppColor.error,
enableFeedback: true,
side: BorderSide(color: AppColor.error, width: 1),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
onPressed: () {
Get.back();
},
child: Text('لغو'),
),
);
},
message: 'تایید یکجا',
icon: Assets.vec.clipboardTaskSvg.svg(width: 40.w, height: 40.h),
backgroundColor: controller.bgConfirmAllColor.value,
),
);
}, controller.allocatedList),
],
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
);
}
Row itemListWidget(AllocatedMadeModel item) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 20),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
controller.getBuyerInformation(item)?.user?.fullname ?? 'N/Aشششش',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
SizedBox(height: 2),
Text(
item.createDate?.formattedJalaliDate ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 3,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 6,
children: [
Visibility(
visible: item.product?.name?.contains('مرغ گرم') ?? false,
child: Assets.vec.hotChickenSvg.svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
Text(
'${item.weightOfCarcasses?.separatedByComma}kg',
textAlign: TextAlign.left,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
],
),
SizedBox(height: 2),
Text(
'${item.amount.separatedByComma} ریال',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.darkGreyDark),
),
],
),
),
SizedBox(width: 8),
Expanded(
flex: 1,
child: Assets.vec.scanSvg.svg(
width: 32.w,
height: 32.h,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
],
);
}
Container itemListExpandedWidget(AllocatedMadeModel item, int index) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
controller.getBuyerInformation(item)?.user?.fullname ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.greenDark),
),
Spacer(),
Text(
item.registrationCode == null ? 'در انتظار' : 'در انتظار تایید خریدار',
textAlign: TextAlign.center,
style: AppFonts.yekan10.copyWith(
color: item.registrationCode == null ? AppColor.darkGreyDark : AppColor.error,
),
),
SizedBox(width: 7),
Assets.vec.clockSvg.svg(
width: 16.w,
height: 16.h,
colorFilter: ColorFilter.mode(
item.registrationCode == null ? AppColor.darkGreyDark : AppColor.error,
BlendMode.srcIn,
),
),
],
),
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: ShapeDecoration(
color: AppColor.blueLight,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: AppColor.blueLightHover),
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 3,
children: [
Text(
item.date?.toJalali.formatter.wN ?? 'N/A',
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),
),
],
),
Text(
'${item.date?.toJalali.formatter.y}',
style: AppFonts.yekan20.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.tHH}:${item.date?.toJalali.formatter.tMM ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
/* buildRow(
title: 'مشخصات خریدار',
value: controller.getBuyerInformation(item)?.user?.fullname ?? 'N/A',
),*/
buildRow(
title: 'تلفن خریدار',
value: controller.getBuyerInformation(item)?.user?.mobile ?? 'N/A',
valueStyle: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
buildRow(title: 'نوع فروش', value: item.sellType?.faItem ?? 'N/A'),
buildRow(title: 'محصول', value: item.product?.name ?? 'N/A'),
buildRow(
title: 'نوع تخصیص',
value: item.allocationType?.faAllocationType ?? 'N/A',
),
buildRow(
title: 'وزن خریداری شده',
value: '${item.weightOfCarcasses?.separatedByComma} کیلوگرم',
),
buildRow(
title: 'افت وزن(کیلوگرم)',
value: item.weightLossOfCarcasses?.toInt().toString() ?? 'N/A',
),
buildRow(title: 'قیمت کل', value: '${item.totalAmount?.separatedByComma} ریال'),
buildRow(title: 'کداحراز', value: item.registrationCode?.toString() ?? 'ندارد'),
buildRow(
title: 'وضعیت کد احراز',
value: item.systemRegistrationCode == true ? "ارسال شده" : "ارسال نشده",
),
Visibility(
visible: item.registrationCode == null,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
RElevated(
text: 'ویرایش',
width: 150.w,
height: 40.h,
onPressed: () {
controller.setEditData(item);
Get.bottomSheet(
addOrEditBottomSheet(true),
isScrollControlled: true,
backgroundColor: Colors.transparent,
).whenComplete(() {
controller.clearForm();
});
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
),
ROutlinedElevated(
text: 'حذف',
textStyle: AppFonts.yekan20.copyWith(color: AppColor.redNormal),
width: 150.w,
height: 40.h,
onPressed: () {
buildDeleteDialog(
onConfirm: () async {
controller.isExpandedList.remove(index);
// controller.denyAllocation(item.key ?? '');
//await controller.deleteAllocation(item);
},
onRefresh: () => controller.getAllocatedMade(),
);
},
borderColor: AppColor.redNormal,
),
],
),
),
],
),
);
}
Widget addOrEditBottomSheet([bool isEditMode = false]) {
return BaseBottomSheet(
height: Get.height * (isEditMode ? 0.45 : 0.75),
child: Form(
key: controller.formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${isEditMode ? 'ویرایش' : 'ثبت'} توزیع/ فروش',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
const SizedBox(height: 12),
productDropDown(),
const SizedBox(height: 12),
Visibility(
visible: isEditMode == false,
child: Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
children: [
const SizedBox(height: 8),
SizedBox(
height: 40,
child: ObxValue((data) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Radio(
value: 1,
groupValue: controller.saleType.value,
onChanged: (value) {
controller.saleType.value = value!;
controller.selectedGuildModel.value = null;
controller.selectedGuildModel.refresh();
},
),
Text('فروش اختصاصی', style: AppFonts.yekan14),
SizedBox(width: 12),
Radio(
value: 2,
groupValue: controller.saleType.value,
onChanged: (value) {
controller.saleType.value = value!;
},
),
Text('فروش آزاد', style: AppFonts.yekan14),
],
);
}, controller.saleType),
),
const SizedBox(height: 12),
guildsDropDown(),
],
),
),
),
const SizedBox(height: 12),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 12,
children: [
Visibility(
visible: isEditMode == false,
child: Column(
children: [
const SizedBox(height: 8),
ObxValue((data) {
return RTextField(
controller: TextEditingController(),
filledColor: AppColor.bgLight,
filled: true,
label: 'تاریخ',
onTap: () {
Get.bottomSheet(
modalDatePicker((value) {
controller.fromDateFilter.value = value;
controller.fromDateFilter.refresh();
}),
);
},
borderColor: AppColor.darkGreyLight,
initText: (data.value ?? Jalali.now()).formatCompactDate(),
);
}, controller.fromDateFilter),
],
),
),
RTextField(
controller: controller.weightController,
keyboardType: TextInputType.number,
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
validator: (value) {
if (int.parse(value!.clearComma) >
(controller.rootLogic.inventoryModel.value?.totalRemainWeight?.toInt() ??
100)) {
return 'وزن تخصیصی بیشتر از موجودی انبار است';
}
return null;
},
onChanged: (p0) {
controller.weight.value = int.tryParse(p0.clearComma) ?? 0;
},
label: 'وزن لاشه',
),
RTextField(
controller: controller.pricePerKiloController,
borderColor: AppColor.darkGreyLight,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
filledColor: AppColor.bgLight,
filled: true,
onChanged: (p0) {
controller.pricePerKilo.value = int.tryParse(p0.clearComma) ?? 0;
},
keyboardType: TextInputType.number,
label: 'قیمت هر کیلو',
),
RTextField(
variant: RTextFieldVariant.noBorder,
enabled: false,
keyboardType: TextInputType.number,
filledColor: AppColor.bgLight,
filled: true,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
controller: controller.totalCostController,
label: 'هزینه کل',
),
ObxValue((data) {
return RElevated(
text: isEditMode ? 'ویرایش' : 'ثبت',
isFullWidth: true,
textStyle: AppFonts.yekan16.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
height: 40,
onPressed: data.value
? () async {
if (isEditMode) {
await controller.updateAllocation();
controller.clearForm();
controller.getAllocatedMade();
controller.rootLogic.getInventory();
Get.back();
} else {
if (controller.formKey.currentState?.validate() ?? false) {
controller.setSubmitData();
await Get.bottomSheet(show2StepAddBottomSheet());
}
}
}
: null,
);
}, controller.isValid),
],
),
),
const SizedBox(height: 20),
],
),
),
);
}
Widget guildsDropDown() {
return Obx(() {
final item = controller.selectedGuildModel.value;
return OverlayDropdownWidget<GuildModel>(
key: ValueKey(item?.user?.fullname ?? ''),
items: controller.guildsModel,
onChanged: (value) {
controller.selectedGuildModel.value = value;
},
selectedItem: item,
itemBuilder: (item) => Text(
item.user != null
? '${item.steward == true ? 'مباشر' : 'صنف'} ${item.user!.fullname} (${item.user!.mobile})'
: 'بدون نام',
),
labelBuilder: (item) => Text(
item?.user != null
? '${item?.steward == true ? 'مباشر' : 'صنف'} ${item?.user!.fullname} (${item?.user!.mobile})'
: 'انتخاب مباشر/صنف',
),
);
});
}
Widget productDropDown() {
return Obx(() {
return OverlayDropdownWidget<ProductModel>(
items: controller.rolesProductsModel,
height: 56,
hasDropIcon: false,
background: Colors.white,
onChanged: (value) {
controller.selectedProductModel.value = value;
},
selectedItem: controller.selectedProductModel.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Row(
spacing: 8,
children: [
(item?.name?.contains('مرغ گرم') ?? false)
? Assets.images.chicken.image(width: 40, height: 40)
: Assets.vec.placeHolderSvg.svg(width: 40, height: 40),
Text(item?.name ?? 'انتخاب محصول'),
Spacer(),
Text(
'موجودی:${controller.rootLogic.inventoryModel.value?.totalRemainWeight.separatedByComma ?? 0}',
),
],
),
);
});
}
Widget filterBottomSheet() {
return BaseBottomSheet(
height: 200,
child: Column(
spacing: 16,
children: [
Text('فیلترها', style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal)),
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,
),
),
],
),
RElevated(
text: 'اعمال فیلتر',
isFullWidth: true,
backgroundColor: AppColor.greenNormal,
onPressed: () {
controller.getAllocatedMade();
Get.back();
},
height: 40,
),
],
),
);
}
GestureDetector timeFilterWidget({
isFrom = true,
required Rxn<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() ?? Jalali.now().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;
},
),
),
),
],
),
);
}
Widget show2StepAddBottomSheet() {
return BaseBottomSheet(
height: Get.height * .39,
child: Column(
spacing: 8,
children: [
buildRow(
title: 'تاریخ ثبت',
value: controller.tmpStewardAllocation?.date?.formattedJalaliDate ?? 'N/A',
),
buildRow(
title: 'نام و نام خانوادگی خریدار',
value:
controller.guildsModel
.firstWhere((p0) => p0.key == controller.tmpStewardAllocation?.guildKey)
.user
?.fullname ??
'N/A',
),
buildRow(
title: 'شماره خریدار',
value:
controller.guildsModel
.firstWhere((p0) => p0.key == controller.tmpStewardAllocation?.guildKey)
.user
?.mobile ??
'N/A',
),
buildRow(
title: 'قیمت هر کیلو',
value: '${controller.tmpStewardAllocation?.amount.separatedByComma ?? 0} ریال ',
),
buildRow(
title: 'وزن تخصیصی',
value:
'${controller.tmpStewardAllocation?.weightOfCarcasses?.toInt().separatedByComma ?? 0} کیلوگرم',
),
buildRow(
title: 'قیمت کل',
value: '${controller.tmpStewardAllocation?.totalAmount.separatedByComma ?? 0} ریال',
),
Row(
spacing: 10,
children: [
Expanded(
child: RElevated(
backgroundColor: AppColor.greenNormal,
height: 40,
text: 'ثبت',
textStyle: AppFonts.yekan18.copyWith(color: Colors.white),
onPressed: () async {
await controller.submitAllocation();
Get
..back()
..back();
},
),
),
Expanded(
child: ROutlinedElevated(
height: 40,
borderColor: AppColor.error,
text: ' بازگشت',
textStyle: AppFonts.yekan18.copyWith(color: AppColor.error),
onPressed: () {
Get
..back()
..back();
},
),
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,214 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/request/steward_free_sale_bar/steward_free_sale_bar_request.dart';
import 'package:rasadyar_chicken/data/models/response/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/steward/root/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/sale/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/sales_out_of_province_buyers/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/sales_out_of_province_sales_list/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
class SalesOutOfProvinceLogic extends GetxController {
StewardRootLogic get rootLogic => Get.find<StewardRootLogic>();
SaleLogic get saleLogic => Get.find<SaleLogic>();
SalesOutOfProvinceBuyersLogic get buyersLogic => Get.find<SalesOutOfProvinceBuyersLogic>();
SalesOutOfProvinceSalesListLogic get saleListLogic =>
Get.find<SalesOutOfProvinceSalesListLogic>();
SalesOutOfProvinceBuyersLogic get buyerLogic => Get.find<SalesOutOfProvinceBuyersLogic>();
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();
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();
routesName.value = [...saleLogic.routesName, 'خارج استان'].toList();
}
@override
void onReady() {
super.onReady();
getOutProvinceSales();
selectedProduct.value = rootLogic.rolesProductsModel.first;
debounce(
searchedValue,
(callback) => getOutProvinceSales(),
time: Duration(milliseconds: timeDebounce),
);
setupListeners();
}
void setSearchValue(String? value) {
searchedValue.value = value?.trim();
}
void submitFilter() {
fromDateFilter.value = fromDateFilter.value;
toDateFilter.value = toDateFilter.value;
getOutProvinceSales();
}
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 = rootLogic.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,525 @@
import 'package:flutter/cupertino.dart';
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/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/filter_bottom_sheet.dart';
import 'package:rasadyar_chicken/presentation/widget/steward/inventory_widget.dart';
import 'package:rasadyar_chicken/presentation/widget/page_route.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class SalesOutOfProvincePage extends GetView<SalesOutOfProvinceLogic> {
const SalesOutOfProvincePage({super.key});
@override
Widget build(BuildContext context) {
return BasePage(
routesWidget: ObxValue((route) => buildPageRoute(route), controller.routesName),
onBackPressed: () => Get.back(id: 1),
onSearchChanged: (data) => controller.setSearchValue(data),
filteringWidget: filterBottomSheet(),
widgets: [
inventoryWidget(controller.rootLogic),
Expanded(
child: ObxValue((data) {
return RPaginatedListView(
onLoadMore: () async => controller.getOutProvinceSales(true),
onRefresh: () async {
controller.currentPage.value = 1;
await controller.getOutProvinceSales();
},
hasMore: data.value.data?.next != null,
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 ExpandableListItem2(
selected: val.contains(index),
onTap: () => controller.isExpandedList.toggle(index),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item, index),
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: Row(
children: [
RFab.add(
onPressed: () {
Get.bottomSheet(addOrEditSaleBottomSheet(), isScrollControlled: true);
},
),
Spacer(),
RFab(
icon: Icon(CupertinoIcons.person_add_solid, color: Colors.white, size: 35.w),
backgroundColor: AppColor.blueNormal,
onPressed: () {
Get.toNamed(ChickenRoutes.salesOutOfProvinceBuyerSteward, id: 1);
},
),
SizedBox(width: 25),
],
),
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
);
}
/* Padding segmentWidget() {
return Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
child: Row(
children: [
Expanded(
child: RSegment(
children: ['فروش', 'خریداران'],
selectedIndex: controller.selectedSegmentIndex.value,
borderColor: const Color(0xFFB4B4B4),
selectedBorderColor: AppColor.blueNormal,
selectedBackgroundColor: AppColor.blueLight,
onSegmentSelected: (index) => controller.selectedSegmentIndex.value = index,
backgroundColor: AppColor.whiteGreyNormal,
),
),
],
),
);
}*/
Widget filterBottomSheet() => filterBottomSheetWidget(
fromDate: controller.fromDateFilter,
onChangedFromDate: (jalali) => controller.fromDateFilter.value = jalali,
toDate: controller.toDateFilter,
onChangedToDate: (jalali) => controller.toDateFilter.value = jalali,
onSubmit: () => controller.submitFilter(),
);
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(
mainAxisAlignment: MainAxisAlignment.center,
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(
mainAxisAlignment: MainAxisAlignment.center,
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, int index) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'${item.province} - ${item.city}',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.greenDark),
),
],
),
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: ShapeDecoration(
color: AppColor.blueLight,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: AppColor.blueLightHover),
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 3,
children: [
Text(
item.date?.toJalali.formatter.wN ?? 'N/A',
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),
),
],
),
Text(
'${item.date?.toJalali.formatter.y}',
style: AppFonts.yekan20.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.tHH}:${item.date?.toJalali.formatter.tMM ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
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}'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
RElevated(
text: 'ویرایش',
width: 150.w,
height: 40.h,
onPressed: () {
controller.setEditDataSales(item);
Get.bottomSheet(
addOrEditSaleBottomSheet(true),
isScrollControlled: true,
).whenComplete(() {
controller.resetSubmitForm();
});
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
),
ROutlinedElevated(
text: 'حذف',
textStyle: AppFonts.yekan20.copyWith(color: AppColor.redNormal),
width: 150.w,
height: 40.h,
onPressed: () {
buildDeleteDialog(
onConfirm: () async {
controller.isExpandedList.remove(index);
controller.deleteStewardPurchaseOutOfProvince(item.key!);
},
onRefresh: () => controller.getOutProvinceSales(),
);
},
borderColor: AppColor.redNormal,
),
],
),
],
),
);
}
Widget addOrEditSaleBottomSheet([bool isOnEdit = false]) {
return BaseBottomSheet(
height: 500.h,
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 16,
children: [
Text(
isOnEdit ? 'ویرایش فروش' : 'افزودن فروش',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
_productDropDown(),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 12,
children: [
Row(
spacing: 8,
children: [
Expanded(
child: timeFilterWidget(
date: controller.saleDate,
onChanged: (jalali) => controller.saleDate.value = jalali,
),
),
],
),
_buyerWidget(),
RTextField(
controller: controller.saleWeightController,
label: 'وزن لاشه',
keyboardType: TextInputType.number,
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
validator: (value) {
if (value == null) {
return 'لطفاً وزن لاشه را وارد کنید';
}
return null;
},
),
RTextField(
controller: controller.quarantineCodeController,
label: 'کد قرنطینه',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
validator: (value) {
if (value == null) {
return 'لطفاً کد قرنطینه را وارد کنید';
}
return null;
},
),
submitButtonWidget(isOnEdit),
],
),
),
SizedBox(),
],
),
),
),
);
}
Widget submitButtonWidget(bool isOnEdit) {
return ObxValue((data) {
return RElevated(
isFullWidth: true,
backgroundColor: AppColor.greenNormal,
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 _productDropDown() {
return Obx(() {
return OverlayDropdownWidget<ProductModel>(
items: controller.rootLogic.rolesProductsModel,
height: 56,
hasDropIcon: false,
background: Colors.white,
onChanged: (value) {
controller.selectedProduct.value = value;
},
selectedItem: controller.selectedProduct.value,
initialValue: controller.selectedProduct.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Row(
spacing: 8,
children: [
(item?.name?.contains('مرغ گرم') ?? false)
? Assets.images.chicken.image(width: 40, height: 40)
: Assets.vec.placeHolderSvg.svg(width: 40, height: 40),
Text(item?.name ?? 'انتخاب محصول'),
Spacer(),
Text(
'موجودی:${controller.rootLogic.inventoryModel.value?.totalRemainWeight.separatedByComma ?? 0}',
),
],
),
);
});
}
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: 40,
decoration: BoxDecoration(
color: AppColor.bgLight,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 1, color: AppColor.darkGreyLight),
),
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

@@ -0,0 +1,211 @@
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/steward/root/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/sale/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/sales_out_of_province/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
class SalesOutOfProvinceBuyersLogic extends GetxController {
StewardRootLogic get rootLogic => Get.find<StewardRootLogic>();
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;
RxList<String> routesName = RxList();
@override
void onInit() {
super.onInit();
routesName.value = [...saleLogic.routesName, 'خریداران'].toList();
getOutProvinceCarcassesBuyer();
}
@override
void onReady() {
super.onReady();
selectedProvince.listen((p0) => getCites());
debounce(
searchedValue,
(callback) => getOutProvinceCarcassesBuyer(),
time: Duration(milliseconds: timeDebounce),
);
setupListeners();
}
@override
void 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;
}
void setSearchValue(String? value) {
searchedValue.value = value?.trim();
}
void submitFilter() {
fromDateFilter.value = fromDateFilter.value;
toDateFilter.value = toDateFilter.value;
getOutProvinceCarcassesBuyer();
}
}

View File

@@ -0,0 +1,320 @@
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/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/filter_bottom_sheet.dart';
import 'package:rasadyar_chicken/presentation/widget/page_route.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class SalesOutOfProvinceBuyersPage extends GetView<SalesOutOfProvinceBuyersLogic> {
const SalesOutOfProvinceBuyersPage({super.key});
@override
Widget build(BuildContext context) {
return BasePage(
routesWidget: ObxValue((route) => buildPageRoute(route), controller.routesName),
onBackPressed: () => Get.back(id: 1),
onSearchChanged: (data) => controller.setSearchValue(data),
filteringWidget: filterBottomSheet(),
widgets: [
Expanded(
child: ObxValue((data) {
return RPaginatedListView(
onLoadMore: () async => controller.getOutProvinceCarcassesBuyer(true),
onRefresh: () async {
controller.currentPage.value = 1;
await controller.getOutProvinceCarcassesBuyer();
},
hasMore: data.value.data?.next != null,
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 ExpandableListItem2(
selected: val.contains(index),
onTap: () => controller.isExpandedList.toggle(index),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item),
labelColor: AppColor.blueLight,
labelIcon: Assets.vec.userRaduisSvg.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,
ignoreSafeArea: false,
).whenComplete(() => controller.resetSubmitForm());
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
);
}
Widget addOrEditBuyerBottomSheet([bool isOnEdit = false]) {
return BaseBottomSheet(
height: 600,
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 8,
children: [
Text(
isOnEdit ? 'ویرایش خریدار' : 'افزودن خریدار',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(spacing: 12, children: [_provinceWidget(), _cityWidget()]),
),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 12,
children: [
RTextField(
controller: controller.buyerNameController,
label: 'نام خریدار',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
RTextField(
controller: controller.buyerLastNameController,
label: 'نام خانوادگی خریدار',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
RTextField(
controller: controller.buyerPhoneController,
label: 'تلفن خریدار',
keyboardType: TextInputType.phone,
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
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.buyerUnitNameController,
label: 'نام واحد',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
submitButtonWidget(isOnEdit),
],
),
),
SizedBox(),
],
),
),
),
);
}
Widget submitButtonWidget(bool isOnEdit) {
return ObxValue((data) {
return RElevated(
isFullWidth: true,
backgroundColor: AppColor.greenNormal,
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 Padding(
padding: const EdgeInsets.only(right: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 8),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
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.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
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}'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
RElevated(
text: 'ویرایش',
width: 150.w,
height: 40.h,
onPressed: () {
controller.setEditDataBuyer(item);
Get.bottomSheet(
addOrEditBuyerBottomSheet(true),
isScrollControlled: true,
).whenComplete(() => controller.resetSubmitForm());
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
),
],
),
],
),
);
}
Widget filterBottomSheet() => filterBottomSheetWidget(
fromDate: controller.fromDateFilter,
onChangedFromDate: (jalali) => controller.fromDateFilter.value = jalali,
toDate: controller.toDateFilter,
onChangedToDate: (jalali) => controller.toDateFilter.value = jalali,
onSubmit: () => controller.submitFilter(),
);
}

View File

@@ -0,0 +1,214 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/request/steward_free_sale_bar/steward_free_sale_bar_request.dart';
import 'package:rasadyar_chicken/data/models/response/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/steward/root/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/sale/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/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 {
StewardRootLogic get rootLogic => Get.find<StewardRootLogic>();
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();
selectedProduct.value = rootLogic.rolesProductsModel.first;
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 = rootLogic.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,490 @@
import 'package:flutter/cupertino.dart';
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/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/widget/list_item/list_item.dart' hide ListItem2;
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();
},
hasMore: data.value.data?.next != null,
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 ExpandableListItem2(
selected: val.contains(index),
onTap: () => controller.isExpandedList.toggle(index),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item, index),
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: Row(
children: [
RFab.add(
onPressed: () {
Get.bottomSheet(
addOrEditSaleBottomSheet(),
ignoreSafeArea: false,
isScrollControlled: true,
).whenComplete(() {
controller.clearSaleForm();
});
},
),
Spacer(),
RFab(
icon: Icon(CupertinoIcons.person_add_solid, color: Colors.white, size: 35.w),
backgroundColor: AppColor.blueNormal,
onPressed: () {
Get.toNamed(ChickenRoutes.salesOutOfProvinceBuyerSteward, id: 1);
},
),
SizedBox(width: 25),
],
),
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(
mainAxisAlignment: MainAxisAlignment.center,
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(
mainAxisAlignment: MainAxisAlignment.center,
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, int index) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'${item.province}-${item.city}',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.greenDark),
),
],
),
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: ShapeDecoration(
color: AppColor.blueLight,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: AppColor.blueLightHover),
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 3,
children: [
Text(
item.date?.toJalali.formatter.wN ?? 'N/A',
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),
),
],
),
Text(
'${item.date?.toJalali.formatter.y}',
style: AppFonts.yekan20.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.tHH}:${item.date?.toJalali.formatter.tMM ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
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}'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
RElevated(
text: 'ویرایش',
width: 150.w,
height: 40.h,
onPressed: () {
controller.setEditDataSales(item);
Get.bottomSheet(
addOrEditSaleBottomSheet(true),
isScrollControlled: true,
).whenComplete(() {
controller.resetSubmitForm();
});
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
),
ROutlinedElevated(
text: 'حذف',
textStyle: AppFonts.yekan20.copyWith(color: AppColor.redNormal),
width: 150.w,
height: 40.h,
onPressed: () {
buildDeleteDialog(
onConfirm: () async {
controller.isExpandedList.remove(index);
controller.deleteStewardPurchaseOutOfProvince(item.key!);
},
onRefresh: () => controller.getOutProvinceSales(),
);
},
borderColor: AppColor.redNormal,
),
],
),
],
),
);
}
Widget addOrEditSaleBottomSheet([bool isOnEdit = false]) {
return BaseBottomSheet(
height: 500.h,
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 16,
children: [
Text(
isOnEdit ? 'ویرایش فروش' : 'افزودن فروش',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
_productDropDown(),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 12,
children: [
Row(
spacing: 8,
children: [
Expanded(
child: timeFilterWidget(
date: controller.saleDate,
onChanged: (jalali) => controller.saleDate.value = jalali,
),
),
],
),
_buyerWidget(),
RTextField(
controller: controller.saleWeightController,
label: 'وزن لاشه',
keyboardType: TextInputType.number,
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
validator: (value) {
if (value == null) {
return 'لطفاً وزن لاشه را وارد کنید';
}
return null;
},
),
RTextField(
controller: controller.quarantineCodeController,
label: 'کد قرنطینه',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
validator: (value) {
if (value == null) {
return 'لطفاً کد قرنطینه را وارد کنید';
}
return null;
},
),
submitButtonWidget(isOnEdit),
],
),
),
SizedBox(),
],
),
),
),
);
}
Widget submitButtonWidget(bool isOnEdit) {
return ObxValue((data) {
return RElevated(
isFullWidth: true,
backgroundColor: AppColor.greenNormal,
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 _productDropDown() {
return Obx(() {
return OverlayDropdownWidget<ProductModel>(
items: controller.rootLogic.rolesProductsModel,
height: 56,
hasDropIcon: false,
background: Colors.white,
onChanged: (value) {
controller.selectedProduct.value = value;
},
selectedItem: controller.selectedProduct.value,
initialValue: controller.selectedProduct.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Row(
spacing: 8,
children: [
(item?.name?.contains('مرغ گرم') ?? false)
? Assets.images.chicken.image(width: 40, height: 40)
: Assets.vec.placeHolderSvg.svg(width: 40, height: 40),
Text(item?.name ?? 'انتخاب محصول'),
Spacer(),
Text(
'موجودی:${controller.rootLogic.inventoryModel.value?.totalRemainWeight.separatedByComma ?? 0}',
),
],
),
);
});
}
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: 40,
decoration: BoxDecoration(
color: AppColor.bgLight,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 1, color: AppColor.darkGreyLight),
),
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

@@ -0,0 +1,215 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/response/guild/guild_model.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/segmentation_model/segmentation_model.dart';
import 'package:rasadyar_chicken/presentation/pages/steward/root/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
class SegmentationLogic extends GetxController {
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
RxBool isLoadingMoreAllocationsMade = false.obs;
RxInt currentPage = 1.obs;
late List<String> routesName;
RxInt selectedSegmentIndex = 0.obs;
RxBool isExpanded = false.obs;
RxList<int> isExpandedList = <int>[].obs;
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
RxnString searchedValue = RxnString();
RxInt saleType = 1.obs;
GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController weightController = TextEditingController(text: '0');
RxBool isSubmitButtonEnabled = false.obs;
Rxn<GuildModel> selectedGuildModel = Rxn<GuildModel>();
Rxn<ProductModel> selectedProduct = Rxn<ProductModel>();
Rxn<SegmentationModel> selectedSegment = Rxn<SegmentationModel>();
Rx<Resource<PaginationModel<SegmentationModel>>> segmentationList =
Resource<PaginationModel<SegmentationModel>>.loading().obs;
RxList<GuildModel> guildsModel = <GuildModel>[].obs;
@override
void onInit() {
super.onInit();
routesName = ['قطعه‌بندی'].toList();
once(rootLogic.rolesProductsModel, (callback) => selectedProduct.value = callback.first);
getAllSegmentation();
getGuilds();
}
@override
void onReady() {
super.onReady();
setUpListener();
}
@override
void onClose() {
// TODO: implement onClose
super.onClose();
}
void setSearchValue(String? value) {
searchedValue.value = value?.trim();
}
void setUpListener() {
debounce(
searchedValue,
(callback) => getAllSegmentation(),
time: Duration(milliseconds: timeDebounce),
);
ever(selectedSegment, (_) {
validateForm();
});
weightController.addListener(() => validateForm());
}
void setEditData(SegmentationModel item) {
selectedSegment.value = item;
weightController.text = item.weight.toString();
}
void clearForm() {
weightController.text = '0';
selectedSegment.value = null;
selectedGuildModel.value = null;
}
void validateForm() {
var weight = int.tryParse(weightController.text.clearComma.trim());
isSubmitButtonEnabled.value =
selectedProduct.value != null &&
weightController.text.isNotEmpty &&
weight! > 0 &&
(saleType.value == 1 || (saleType.value == 2 && selectedGuildModel.value != null));
}
Future<void> getAllSegmentation([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
segmentationList.value = Resource<PaginationModel<SegmentationModel>>.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: () async => await rootLogic.chickenRepository.getSegmentation(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
pageSize: 20,
page: currentPage.value,
search: 'filter',
role: 'Steward',
value: searchedValue.value,
fromDate: fromDateFilter.value.toDateTime(),
toDate: toDateFilter.value.toDateTime(),
),
),
onSuccess: (result) {
if ((result?.count ?? 0) == 0) {
segmentationList.value = Resource<PaginationModel<SegmentationModel>>.empty();
} else {
segmentationList.value = Resource<PaginationModel<SegmentationModel>>.success(
PaginationModel<SegmentationModel>(
count: result?.count ?? 0,
next: result?.next,
previous: result?.previous,
results: [
...(segmentationList.value.data?.results ?? []),
...(result?.results ?? []),
],
),
);
isLoadingMoreAllocationsMade.value = false;
}
},
);
}
Future<void> deleteSegmentation(String key) async {
await safeCall(
showError: false,
call: () => rootLogic.chickenRepository.deleteSegmentation(
token: rootLogic.tokenService.accessToken.value!,
key: key,
),
);
}
Future<bool> editSegment() async {
var res = true;
safeCall(
showError: true,
call: () async => await rootLogic.chickenRepository.editSegmentation(
token: rootLogic.tokenService.accessToken.value!,
model: SegmentationModel(
key: selectedSegment.value?.key,
weight: int.tryParse(weightController.text.clearComma) ?? 0,
),
),
onSuccess: (result) {
res = true;
},
onError: (error, stacktrace) {
res = false;
},
);
return res;
}
Future<bool> createSegment() async {
var res = true;
SegmentationModel segmentationModel = SegmentationModel(
productKey: selectedProduct.value?.key,
weight: int.tryParse(weightController.text.clearComma) ?? 0,
);
if (saleType.value == 2) {
segmentationModel = segmentationModel.copyWith(guildKey: selectedGuildModel.value?.key);
}
await safeCall(
call: () async => await rootLogic.chickenRepository.createSegmentation(
token: rootLogic.tokenService.accessToken.value!,
model: segmentationModel,
),
onSuccess: (result) {
res = true;
},
onError: (error, stacktrace) {
res = false;
},
);
return res;
}
Future<void> getGuilds() async {
safeCall(
call: () async => await rootLogic.chickenRepository.getGuilds(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(queryParams: {'all': true}, role: 'Steward'),
),
onSuccess: (result) {
if (result != null) {
guildsModel.clear();
guildsModel.addAll(result);
}
},
onError: (error, stacktrace) {},
);
}
}

View File

@@ -0,0 +1,430 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rasadyar_chicken/data/models/response/guild/guild_model.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/segmentation_model/segmentation_model.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/filter_bottom_sheet.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class SegmentationPage extends GetView<SegmentationLogic> {
@override
Widget build(BuildContext context) {
return BasePage(
routes: controller.routesName,
onSearchChanged: (data) => controller.setSearchValue(data),
filteringWidget: filterBottomSheet(),
hasBack: false,
widgets: [
Expanded(
child: ObxValue((data) {
return RPaginatedListView(
onLoadMore: () async => controller.getAllSegmentation(true),
onRefresh: () async {
controller.currentPage.value = 1;
await controller.getAllSegmentation();
},
hasMore: data.value.data?.next != null,
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 ExpandableListItem2(
selected: val.contains(index),
onTap: () => controller.isExpandedList.toggle(index),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item, index),
labelColor: AppColor.blueLight,
labelIconColor: AppColor.customGrey,
labelIcon: Assets.vec.convertCubeSvg.path,
);
}, controller.isExpandedList);
},
itemCount: data.value.data?.results?.length ?? 0,
separatorBuilder: (context, index) => SizedBox(height: 8.h),
);
}, controller.segmentationList),
),
],
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
floatingActionButton: RFab.add(
onPressed: () {
Get.bottomSheet(
addOrEditBottomSheet(),
isScrollControlled: true,
ignoreSafeArea: false,
).whenComplete(() {
controller.clearForm();
});
},
),
);
}
Widget filterBottomSheet() => filterBottomSheetWidget(
fromDate: controller.fromDateFilter,
onChangedFromDate: (jalali) => controller.fromDateFilter.value = jalali,
toDate: controller.toDateFilter,
onChangedToDate: (jalali) => controller.toDateFilter.value = jalali,
onSubmit: () => controller.getAllSegmentation(),
);
itemListWidget(SegmentationModel item) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 12),
Expanded(
flex: 3,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
item.toGuild != null ? 'مباشر' : 'قطعه‌بند',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
Text(
item.date?.formattedJalaliDate ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
],
),
),
SizedBox(width: 4),
Expanded(
flex: 5,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
item.toGuild != null
? item.toGuild?.user?.fullname ?? 'N/A'
: item.buyer?.fullname ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
SizedBox(height: 2),
Text(
item.toGuild != null
? item.toGuild?.guildsName ?? 'N/A'
: item.buyer?.shop ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
],
),
),
SizedBox(width: 4),
Expanded(
flex: 2,
child: Text(
'${item.weight.separatedByComma} KG',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
),
],
);
}
itemListExpandedWidget(SegmentationModel item, int index) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: ShapeDecoration(
color: AppColor.blueLight,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: AppColor.blueLightHover),
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 3,
children: [
Text(
DateTimeExtensions(item.date)?.toJalali().formatter.wN ?? 'N/A',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
Text(
'${DateTimeExtensions(item.date)?.toJalali().formatter.d} ${DateTimeExtensions(item.date)?.toJalali().formatter.mN ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
],
),
Text(
'${DateTimeExtensions(item.date)?.toJalali().formatter.y}',
style: AppFonts.yekan20.copyWith(color: AppColor.textColor),
),
Text(
'${DateTimeExtensions(item.date)?.toJalali().formatter.tHH}:${DateTimeExtensions(item.date)?.toJalali().formatter.tMM ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
buildRow(
title: 'مشخصات خریدار',
value: item.toGuild != null
? item.toGuild?.user?.fullname ?? 'N/A'
: item.buyer?.fullname ?? 'N/A',
),
buildRow(
title: 'تلفن خریدار',
value: item.toGuild != null
? item.toGuild?.user?.mobile ?? 'N/A'
: item.buyer?.mobile ?? 'N/A',
),
buildRow(
title: 'نام واحد',
value: item.toGuild != null
? item.toGuild?.guildsName ?? 'N/A'
: item.buyer?.shop ?? 'N/A',
),
buildRow(title: 'ماهیت', value: item.toGuild != null ? 'مباشر' : 'قطعه‌بند'),
buildRow(title: 'وزن قطعه‌بندی', value: '${item.weight?.separatedByComma}'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
RElevated(
text: 'ویرایش',
width: 150.w,
height: 40.h,
onPressed: () {
controller.setEditData(item);
Get.bottomSheet(
addOrEditBottomSheet(true),
isScrollControlled: true,
ignoreSafeArea: false,
).whenComplete(() {
controller.clearForm();
});
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
),
ROutlinedElevated(
text: 'حذف',
textStyle: AppFonts.yekan20.copyWith(color: AppColor.redNormal),
width: 150.w,
height: 40.h,
onPressed: () {
buildDeleteDialog(
onConfirm: () async {
controller.isExpandedList.remove(index);
controller.deleteSegmentation(item.key!);
},
onRefresh: () => controller.getAllSegmentation(),
);
},
borderColor: AppColor.redNormal,
),
],
),
],
),
);
}
Widget addOrEditBottomSheet([bool isOnEdit = false]) {
return BaseBottomSheet(
height: 430.h,
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 16,
children: [
Text(
isOnEdit ? 'ویرایش قطعه‌بندی' : 'افزودن قطعه‌بندی',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
_productDropDown(),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
children: [
const SizedBox(height: 8),
SizedBox(
height: 40,
child: ObxValue((data) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Radio(
value: 1,
groupValue: controller.saleType.value,
onChanged: (value) {
controller.saleType.value = value!;
controller.selectedGuildModel.value = null;
controller.selectedGuildModel.refresh();
},
),
Text('قطعه‌بندی(مباشر)', style: AppFonts.yekan14),
SizedBox(width: 12),
Radio(
value: 2,
groupValue: controller.saleType.value,
onChanged: (value) {
controller.saleType.value = value!;
},
),
Text('تخصیص به قطعه‌بند', style: AppFonts.yekan14),
],
);
}, controller.saleType),
),
const SizedBox(height: 12),
guildsDropDown(),
],
),
),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 12,
children: [
UnitTextField(
hint: 'وزن',
unit: 'کیلوگرم',
controller: controller.weightController,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
validator: (value) {
if (value == null) {
return 'لطفاً وزن لاشه را وارد کنید';
}
return null;
},
),
submitButtonWidget(isOnEdit),
],
),
),
SizedBox(),
],
),
),
),
);
}
Widget submitButtonWidget(bool isOnEdit) {
return ObxValue((data) {
return RElevated(
isFullWidth: true,
backgroundColor: AppColor.greenNormal,
text: isOnEdit ? 'ویرایش' : 'ثبت',
onPressed: data.value
? () async {
var res = isOnEdit
? await controller.editSegment()
: await controller.createSegment();
if (res) {
await controller.getAllSegmentation();
controller.clearForm();
Get.back();
}
}
: null,
height: 40,
);
}, controller.isSubmitButtonEnabled);
}
Widget _productDropDown() {
return Obx(() {
return OverlayDropdownWidget<ProductModel>(
items: controller.rootLogic.rolesProductsModel,
height: 56,
hasDropIcon: false,
background: Colors.white,
onChanged: (value) {
controller.selectedProduct.value = value;
},
selectedItem: controller.selectedProduct.value,
initialValue: controller.selectedProduct.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Row(
spacing: 8,
children: [
(item?.name?.contains('مرغ گرم') ?? false)
? Assets.images.chicken.image(width: 40, height: 40)
: Assets.vec.placeHolderSvg.svg(width: 40, height: 40),
Text(item?.name ?? 'انتخاب محصول'),
Spacer(),
Text(
'موجودی: ${controller.rootLogic.inventoryModel.value?.totalRemainWeight.separatedByComma ?? 0} کیلوگرم',
),
],
),
);
});
}
Widget guildsDropDown() {
return Obx(() {
final item = controller.selectedGuildModel.value;
return OverlayDropdownWidget<GuildModel>(
key: ValueKey(item?.user?.fullname ?? ''),
items: controller.guildsModel,
isDisabled: controller.saleType.value == 1,
onChanged: (value) {
controller.selectedGuildModel.value = value;
},
selectedItem: item,
itemBuilder: (item) => Text(
item.user != null
? '${item.steward == true ? 'مباشر' : 'صنف'} ${item.user!.fullname} (${item.user!.mobile})'
: 'بدون نام',
),
labelBuilder: (item) => Text(
item?.user != null
? '${item?.steward == true ? 'مباشر' : 'صنف'} ${item?.user!.fullname} (${item?.user!.mobile})'
: 'انتخاب مباشر/صنف',
),
);
});
}
}

View File

@@ -0,0 +1,28 @@
export 'buy/logic.dart';
export 'buy/view.dart';
export 'buy_in_province/logic.dart';
export 'buy_in_province/view.dart';
export 'buy_in_province_all/logic.dart';
export 'buy_in_province_all/view.dart';
export 'buy_in_province_waiting/logic.dart';
export 'buy_in_province_waiting/view.dart';
export 'buy_out_of_province/logic.dart';
export 'buy_out_of_province/view.dart';
export 'home/logic.dart';
export 'home/view.dart';
export 'profile/logic.dart';
export 'profile/view.dart';
export 'root/logic.dart';
export 'root/view.dart';
export 'sale/logic.dart';
export 'sale/view.dart';
export 'sales_in_province/logic.dart';
export 'sales_in_province/view.dart';
export 'sales_out_of_province/logic.dart';
export 'sales_out_of_province/view.dart';
export 'sales_out_of_province_buyers/logic.dart';
export 'sales_out_of_province_buyers/view.dart';
export 'sales_out_of_province_sales_list/logic.dart';
export 'sales_out_of_province_sales_list/view.dart';
export 'segmentation/logic.dart';
export 'segmentation/view.dart';