diff --git a/android/local.properties b/android/local.properties index b51c4ef..800da44 100644 --- a/android/local.properties +++ b/android/local.properties @@ -1,5 +1,5 @@ sdk.dir=C:\\Users\\Housh11\\AppData\\Local\\Android\\sdk flutter.sdk=C:\\src\\flutter -flutter.buildMode=debug -flutter.versionName=1.3.35 +flutter.buildMode=release +flutter.versionName=1.3.36 flutter.versionCode=32 \ No newline at end of file diff --git a/packages/chicken/lib/data/common/fa_user_role.dart b/packages/chicken/lib/data/common/fa_user_role.dart index 311bd5d..52e19c2 100644 --- a/packages/chicken/lib/data/common/fa_user_role.dart +++ b/packages/chicken/lib/data/common/fa_user_role.dart @@ -1,5 +1,12 @@ +import 'package:rasadyar_chicken/features/city_jahad/presentation/routes/routes.dart'; import 'package:rasadyar_chicken/features/poultry_science/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/presentation/routes/routes.dart'; import 'package:rasadyar_chicken/features/steward/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/routes/routes.dart'; import 'package:rasadyar_chicken/presentation/routes/routes.dart'; String getFaUserRole(String? role) { @@ -90,7 +97,7 @@ Map getFaUserRoleWithOnTap(String? role) { case "Poultry": return {"مرغدار": null}; case "ProvinceOperator": - return {"مدیر اجرایی": null}; + return {"مدیر اجرایی": ProvinceOperatorRoutes.initProvinceOperator}; case "ProvinceFinancial": return {"مالی اتحادیه": null}; case "KillHouse": @@ -98,17 +105,17 @@ Map getFaUserRoleWithOnTap(String? role) { case "KillHouseVet": return {"دامپزشک کشتارگاه": null}; case "VetFarm": - return {"دامپزشک فارم": null}; + return {"دامپزشک فارم": VetFarmRoutes.initVetFarm}; case "Driver": return {"راننده": null}; case "ProvinceInspector": - return {"بازرس اتحادیه": null}; + return {"بازرس اتحادیه": ProvinceInspectorRoutes.initProvinceInspector}; case "VetSupervisor": return {"دامپزشک کل": null}; case "Jahad": - return {"جهاد کشاورزی استان": null}; + return {"جهاد کشاورزی استان": JahadRoutes.initJahad}; case "CityJahad": - return {"جهاد کشاورزی شهرستان": null}; + return {"جهاد کشاورزی شهرستان": CityJahadRoutes.initCityJahad}; case "ProvincialGovernment": return {"استانداری": null}; case "Guilds": @@ -124,7 +131,7 @@ Map getFaUserRoleWithOnTap(String? role) { case "Observatory": return {"رصدخانه": null}; case "ProvinceSupervisor": - return {"ناظر استان": null}; + return {"ناظر استان": ProvinceSupervisorRoutes.initProvinceSupervisor}; case "GuildRoom": return {"اتاق اصناف": null}; case "PosCompany": @@ -132,7 +139,7 @@ Map getFaUserRoleWithOnTap(String? role) { case "LiveStockSupport": return {"پشتیبانی امور دام": null}; case "SuperAdmin": - return {"ادمین کل": null}; + return {"ادمین کل": SuperAdminRoutes.initSuperAdmin}; case "ChainCompany": return {"شرکت زنجیره": null}; case "AdminX": diff --git a/packages/chicken/lib/data/di/chicken_di.dart b/packages/chicken/lib/data/di/chicken_di.dart index 2a037a2..483852e 100644 --- a/packages/chicken/lib/data/di/chicken_di.dart +++ b/packages/chicken/lib/data/di/chicken_di.dart @@ -7,6 +7,13 @@ import 'package:rasadyar_chicken/data/repositories/kill_house/kill_house_reposit import 'package:rasadyar_chicken/data/repositories/kill_house/kill_house_repository_impl.dart'; import 'package:rasadyar_chicken/features/poultry_science/data/di/poultry_science_di.dart'; import 'package:rasadyar_chicken/features/steward/data/di/steward_di.dart'; +import 'package:rasadyar_chicken/features/province_operator/data/di/province_operator_di.dart'; +import 'package:rasadyar_chicken/features/province_inspector/data/di/province_inspector_di.dart'; +import 'package:rasadyar_chicken/features/city_jahad/data/di/city_jahad_di.dart'; +import 'package:rasadyar_chicken/features/vet_farm/data/di/vet_farm_di.dart'; +import 'package:rasadyar_chicken/features/super_admin/data/di/super_admin_di.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/data/di/province_supervisor_di.dart'; +import 'package:rasadyar_chicken/features/jahad/data/di/jahad_di.dart'; import 'package:rasadyar_core/core.dart'; GetIt diChicken = GetIt.asNewInstance(); @@ -58,6 +65,27 @@ Future setupChickenDI() async { // Setup steward feature DI await setupStewardDI(diChicken, dioRemote); + // Setup province_operator feature DI + await setupProvinceOperatorDI(diChicken, dioRemote); + + // Setup province_inspector feature DI + await setupProvinceInspectorDI(diChicken, dioRemote); + + // Setup city_jahad feature DI + await setupCityJahadDI(diChicken, dioRemote); + + // Setup vet_farm feature DI + await setupVetFarmDI(diChicken, dioRemote); + + // Setup super_admin feature DI + await setupSuperAdminDI(diChicken, dioRemote); + + // Setup province_supervisor feature DI + await setupProvinceSupervisorDI(diChicken, dioRemote); + + // Setup jahad feature DI + await setupJahadDI(diChicken, dioRemote); + //region kill house module DI diChicken.registerLazySingleton( () => KillHouseRemoteDataSourceImpl(diChicken.get()), @@ -103,10 +131,17 @@ Future newSetupAuthDI(String newUrl) async { final dioRemote = diChicken.get(); await dioRemote.init(); - // --- common, poultry_science, steward + // --- common, poultry_science, steward, and other features await setupCommonDI(diChicken, dioRemote); await setupPoultryScienceDI(diChicken, dioRemote); await setupStewardDI(diChicken, dioRemote); + await setupProvinceOperatorDI(diChicken, dioRemote); + await setupProvinceInspectorDI(diChicken, dioRemote); + await setupCityJahadDI(diChicken, dioRemote); + await setupVetFarmDI(diChicken, dioRemote); + await setupSuperAdminDI(diChicken, dioRemote); + await setupProvinceSupervisorDI(diChicken, dioRemote); + await setupJahadDI(diChicken, dioRemote); } Future reRegister(T Function() factory) async { diff --git a/packages/chicken/lib/features/city_jahad/city_jahad.dart b/packages/chicken/lib/features/city_jahad/city_jahad.dart new file mode 100644 index 0000000..f61a0e6 --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/city_jahad.dart @@ -0,0 +1,3 @@ +export 'data/di/city_jahad_di.dart'; +export 'presentation/routes/routes.dart'; +export 'presentation/routes/pages.dart'; diff --git a/packages/chicken/lib/features/city_jahad/data/datasources/remote/city_jahad_remote_data_source.dart b/packages/chicken/lib/features/city_jahad/data/datasources/remote/city_jahad_remote_data_source.dart new file mode 100644 index 0000000..98583e6 --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/data/datasources/remote/city_jahad_remote_data_source.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class CityJahadRemoteDataSource { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/city_jahad/data/datasources/remote/city_jahad_remote_data_source_impl.dart b/packages/chicken/lib/features/city_jahad/data/datasources/remote/city_jahad_remote_data_source_impl.dart new file mode 100644 index 0000000..94950cf --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/data/datasources/remote/city_jahad_remote_data_source_impl.dart @@ -0,0 +1,39 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/city_jahad/data/datasources/remote/city_jahad_remote_data_source.dart'; +import 'package:rasadyar_core/core.dart'; + +class CityJahadRemoteDataSourceImpl implements CityJahadRemoteDataSource { + final DioRemote _httpClient; + + CityJahadRemoteDataSourceImpl(this._httpClient); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + var res = await _httpClient.get( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + queryParameters: queryParameters, + fromJson: (json) => PaginationModel.fromJson( + json, + (json) => PoultryScienceReport.fromJson(json as Map), + ), + ); + return res.data; + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + await _httpClient.post( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + data: request.toJson(), + ); + } +} diff --git a/packages/chicken/lib/features/city_jahad/data/di/city_jahad_di.dart b/packages/chicken/lib/features/city_jahad/data/di/city_jahad_di.dart new file mode 100644 index 0000000..76fd618 --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/data/di/city_jahad_di.dart @@ -0,0 +1,36 @@ +import 'package:rasadyar_chicken/features/city_jahad/data/datasources/remote/city_jahad_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/city_jahad/data/datasources/remote/city_jahad_remote_data_source_impl.dart'; +import 'package:rasadyar_chicken/features/city_jahad/data/repositories/city_jahad_repository.dart'; +import 'package:rasadyar_chicken/features/city_jahad/data/repositories/city_jahad_repository_impl.dart'; +import 'package:rasadyar_core/core.dart'; + +/// Setup dependency injection for city_jahad feature +Future setupCityJahadDI(GetIt di, DioRemote dioRemote) async { + di.registerLazySingleton( + () => CityJahadRemoteDataSourceImpl(dioRemote), + ); + + di.registerLazySingleton( + () => CityJahadRepositoryImpl(di.get()), + ); +} + +/// Re-register city_jahad dependencies (used when base URL changes) +Future reRegisterCityJahadDI(GetIt di, DioRemote dioRemote) async { + await reRegister(di, () => CityJahadRemoteDataSourceImpl(dioRemote)); + await reRegister( + di, + () => CityJahadRepositoryImpl(di.get()), + ); +} + +/// Helper function to re-register a dependency +Future reRegister( + GetIt di, + T Function() factory, +) async { + if (di.isRegistered()) { + await di.unregister(); + } + di.registerLazySingleton(factory); +} diff --git a/packages/chicken/lib/features/city_jahad/data/repositories/city_jahad_repository.dart b/packages/chicken/lib/features/city_jahad/data/repositories/city_jahad_repository.dart new file mode 100644 index 0000000..bd0e67a --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/data/repositories/city_jahad_repository.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class CityJahadRepository { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/city_jahad/data/repositories/city_jahad_repository_impl.dart b/packages/chicken/lib/features/city_jahad/data/repositories/city_jahad_repository_impl.dart new file mode 100644 index 0000000..fabb064 --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/data/repositories/city_jahad_repository_impl.dart @@ -0,0 +1,30 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/city_jahad/data/datasources/remote/city_jahad_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/city_jahad/data/repositories/city_jahad_repository.dart'; +import 'package:rasadyar_core/core.dart'; + +class CityJahadRepositoryImpl implements CityJahadRepository { + final CityJahadRemoteDataSource _remote; + + CityJahadRepositoryImpl(this._remote); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + return await _remote.getSubmitInspectionList( + token: token, + queryParameters: queryParameters, + ); + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + return await _remote.submitInspection(token: token, request: request); + } +} diff --git a/packages/chicken/lib/features/city_jahad/presentation/pages/active_hatching/logic.dart b/packages/chicken/lib/features/city_jahad/presentation/pages/active_hatching/logic.dart new file mode 100644 index 0000000..7809be9 --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/presentation/pages/active_hatching/logic.dart @@ -0,0 +1,95 @@ +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/repositories/poultry_science_repository.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/pages/root/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingLogic extends GetxController { + CityJahadRootLogic rootLogic = Get.find(); + BaseLogic baseLogic = Get.find(); + late PoultryScienceRepository poultryScienceRepository; + Rx>> activeHatchingList = + Resource>.loading().obs; + + final RxBool isLoadingMoreList = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + List routesName = ['اقدام', 'جوجه ریزی فعال']; + + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + RxnString searchedValue = RxnString(); + + @override + void onInit() { + super.onInit(); + poultryScienceRepository = diChicken.get(); + } + + @override + void onReady() { + super.onReady(); + getHatchingList(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getHatchingList([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreList.value = true; + } else { + activeHatchingList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => await poultryScienceRepository.getHatchingPoultry( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + queryParams: {'type': 'hatching'}, + role: 'CityJahad', + pageSize: 50, + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + activeHatchingList.value = + Resource>.empty(); + } else { + activeHatchingList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(activeHatchingList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + Future onRefresh() async { + currentPage.value = 1; + await getHatchingList(); + } +} diff --git a/packages/chicken/lib/features/city_jahad/presentation/pages/active_hatching/view.dart b/packages/chicken/lib/features/city_jahad/presentation/pages/active_hatching/view.dart new file mode 100644 index 0000000..9f8d12a --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/presentation/pages/active_hatching/view.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet_logic.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingPage extends GetView { + const ActiveHatchingPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasSearch: true, + hasFilter: false, + backId: cityJahadActionKey, + routes: controller.routesName, + onSearchChanged: (data) { + controller.searchedValue.value = data; + controller.getHatchingList(); + }, + child: hatchingWidget(), + /*widgets: [ + hatchingWidget() + ],*/ + ); + } + + Widget hatchingWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidget(item), + secondChild: itemListExpandedWidget(item), + labelColor: AppColor.blueLight, + labelIcon: Assets.vec.activeFramSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getHatchingList(true), + ); + }, controller.activeHatchingList); + } + + Container itemListExpandedWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.center, + style: AppFonts.yekan16.copyWith(color: AppColor.greenDark), + ), + Spacer(), + + Visibility( + child: Text( + item.violation == true ? 'پیگیری' : 'عادی', + textAlign: TextAlign.center, + style: AppFonts.yekan10.copyWith(color: AppColor.redDark), + ), + ), + ], + ), + 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: [ + Text( + 'نژاد:${item.breed?.first.breed ?? 'N/A'}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + Text( + ' سن${item.age} (روز)', + + style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal), + ), + Text( + ' دوره جوجه ریزی:${item.period}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ], + ), + ), + + buildRow( + title: 'شماره مجوز جوجه ریزی', + value: item.licenceNumber ?? 'N/A', + ), + buildUnitRow( + title: 'حجم جوجه ریزی', + value: item.quantity.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'مانده در سالن', + value: item.leftOver.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'تلفات', + value: item.losses.separatedByCommaFa, + unit: '(قطعه)', + ), + buildRow( + title: 'دامپزشک فارم', + value: + '${item.vetFarm?.vetFarmFullName}(${item.vetFarm?.vetFarmMobile})', + ), + buildRow( + title: 'شرح بازرسی', + value: item.reportInfo?.image == false + ? 'ارسال تصویر جوجه ریزی فارم ' + : 'تکمیل شده', + titleStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + valueStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + ), + + RElevated( + height: 40.h, + isFullWidth: true, + onPressed: () { + Get.find().setHatchingModel( + item, + ); + Get.bottomSheet( + CreateInspectionBottomSheet(), + isScrollControlled: true, + ignoreSafeArea: false, + ).then((value) { + if (Get.isRegistered()) { + Get.find().clearForm(); + } + }); + }, + child: Text('ثبت بازرسی'), + ), + ], + ), + ); + } + + Widget itemListWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + Text( + item.poultry?.user?.mobile ?? '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: [ + Text( + item.poultry?.unitName ?? 'N/A', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.bgDark), + ), + Text( + item.poultry?.licenceNumber ?? 'N/A', + textAlign: TextAlign.left, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + ], + ), + ), + Expanded( + flex: 1, + child: Assets.vec.scanSvg.svg( + width: 32.w, + height: 32.h, + colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn), + ), + ), + ], + ); + } +} diff --git a/packages/chicken/lib/features/city_jahad/presentation/pages/home/logic.dart b/packages/chicken/lib/features/city_jahad/presentation/pages/home/logic.dart new file mode 100644 index 0000000..dc3671a --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/presentation/pages/home/logic.dart @@ -0,0 +1,29 @@ +import 'package:rasadyar_chicken/features/city_jahad/presentation/routes/routes.dart'; +import 'package:rasadyar_core/core.dart'; + +class CityJahadActionItem { + final String title; + final String route; + final String icon; + + CityJahadActionItem({ + required this.title, + required this.route, + required this.icon, + }); +} + +class CityJahadHomeLogic extends GetxController { + RxList items = [ + CityJahadActionItem( + title: "جوجه ریزی فعال", + route: CityJahadRoutes.activeHatchingCityJahad, + icon: Assets.vec.activeFramSvg.path, + ), + CityJahadActionItem( + title: "بازرسی مزارع طیور", + route: CityJahadRoutes.newInspectionCityJahad, + icon: Assets.vec.activeFramSvg.path, + ), + ].obs; +} diff --git a/packages/chicken/lib/features/city_jahad/presentation/pages/home/view.dart b/packages/chicken/lib/features/city_jahad/presentation/pages/home/view.dart new file mode 100644 index 0000000..7425760 --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/presentation/pages/home/view.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class CityJahadHomePage extends GetView { + CityJahadHomePage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isBase: true, + hasNews: true, + hasNotification: true, + child: gridWidget(), + ); + } + + Widget gridWidget() { + return ObxValue((data) { + return GridView.builder( + physics: BouncingScrollPhysics(), + padding: EdgeInsets.symmetric(vertical: 18.h, horizontal: 32.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 24.h, + crossAxisSpacing: 24.w, + ), + itemCount: data.length, + hitTestBehavior: HitTestBehavior.opaque, + itemBuilder: (BuildContext context, int index) { + var item = data[index]; + return GlassMorphismCardIcon( + title: item.title, + vecIcon: item.icon, + onTap: () async { + Get.toNamed(item.route, id: cityJahadActionKey); + }, + ); + }, + ); + }, controller.items); + } +} diff --git a/packages/chicken/lib/features/city_jahad/presentation/pages/new_inspection/logic.dart b/packages/chicken/lib/features/city_jahad/presentation/pages/new_inspection/logic.dart new file mode 100644 index 0000000..ceb6f4f --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/presentation/pages/new_inspection/logic.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/pages/root/logic.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class NewInspectionLogic extends GetxController { + BaseLogic baseLogic = Get.find(); + + Rx>> submitInspectionList = + Resource>.loading().obs; + + CityJahadRootLogic rootLogic = Get.find(); + + final RxBool isLoadingMoreAllocationsMade = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + RxList pickedImages = [].obs; + final List _multiPartPickedImages = []; + + RxBool isOnUpload = false.obs; + + RxDouble presentUpload = 0.0.obs; + RxList routesName = RxList(); + RxInt selectedSegmentIndex = 0.obs; + + RxnString searchedValue = RxnString(); + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + + @override + void onInit() { + super.onInit(); + + routesName.value = ['اقدام'].toList(); + + ever(selectedSegmentIndex, (callback) { + routesName.removeLast(); + routesName.add(callback == 0 ? 'بازرسی' : 'بایگانی'); + }); + } + + @override + void onReady() { + super.onReady(); + + getReport(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getReport([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreAllocationsMade.value = true; + } else { + submitInspectionList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => + await rootLogic.cityJahadRepository.getSubmitInspectionList( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + role: 'CityJahad', + pageSize: 50, + search: 'filter', + + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + submitInspectionList.value = + Resource>.empty(); + } else { + submitInspectionList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(submitInspectionList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + Future pickImages() async { + determineCurrentPosition(); + var tmp = await pickCameraImage(); + if (tmp?.path != null && pickedImages.length < 7) { + pickedImages.add(tmp!); + } + } + + void removeImage(int index) { + pickedImages.removeAt(index); + } + + void closeBottomSheet() { + Get.back(); + } + + double calculateUploadProgress({required int sent, required int total}) { + if (total != 0) { + double progress = (sent * 100 / total) / 100; + return progress; + } else { + return 0.0; + } + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + void setSearchValue(String? data) { + dLog('Search Value: $data'); + searchedValue.value = data?.trim(); + getReport(); + } + + Future onRefresh() async { + currentPage.value = 1; + await getReport(); + } + + String getStatus(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return 'در حال بررسی'; + } + return status; + } + + Color getStatusColor(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return AppColor.yellowNormal; + } + // می‌توانید منطق رنگ را بر اساس inspectionStatus تنظیم کنید + return AppColor.greenNormal; + } +} diff --git a/packages/chicken/lib/features/city_jahad/presentation/pages/new_inspection/view.dart b/packages/chicken/lib/features/city_jahad/presentation/pages/new_inspection/view.dart new file mode 100644 index 0000000..ea77f82 --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/presentation/pages/new_inspection/view.dart @@ -0,0 +1,1134 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class NewInspectionPage extends GetView { + const NewInspectionPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasBack: true, + hasFilter: true, + hasSearch: true, + onFilterTap: () { + Get.bottomSheet(filterBottomSheet()); + }, + onRefresh: controller.onRefresh, + onSearchChanged: (data) => controller.setSearchValue(data), + backId: cityJahadActionKey, + routesWidget: ContainerBreadcrumb(rxRoutes: controller.routesName), + child: Column(children: [reportWidget()]), + ); + } + + Widget reportWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidgetReport(item), + secondChild: itemListExpandedWidgetReport(item), + labelColor: AppColor.greenLight, + labelIcon: Assets.vec.cubeSearchSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getReport(true), + ); + }, controller.submitInspectionList), + ); + } + + Widget itemListExpandedWidgetReport(PoultryScienceReport item) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + spacing: 8, + children: [ + buildRow( + title: 'وضعیت بازرسی', + value: controller.getStatus(item), + titleStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + valueStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + ), + if (item.hatching?.poultry?.unitName != null) + buildRow( + title: 'مرغداری', + value: item.hatching?.poultry?.unitName ?? '-', + ), + if (item.hatching?.id != null) + buildRow( + title: 'شناسه جوجه‌ریزی', + value: item.hatching!.id.toString(), + ), + + if (item + .reportInformation + ?.technicalOfficer + ?.technicalHealthOfficer != + null) + buildRow( + title: 'کارشناس بهداشت', + value: + item + .reportInformation! + .technicalOfficer! + .technicalHealthOfficer ?? + '-', + ), + if (item + .reportInformation + ?.technicalOfficer + ?.technicalEngineeringOfficer != + null) + buildRow( + title: 'کارشناس فنی', + value: + item + .reportInformation! + .technicalOfficer! + .technicalEngineeringOfficer ?? + '-', + ), + if (item.reportInformation?.casualties?.normalLosses != null || + item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات عادی', + value: (item.reportInformation?.casualties?.normalLosses ?? 0) + .toString(), + unit: '(قطعه)', + ), + if (item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات غیرعادی', + value: item.reportInformation!.casualties!.abnormalLosses + .toString(), + unit: '(قطعه)', + ), + + if (item.reportInformation?.inspectionNotes != null && + item.reportInformation!.inspectionNotes!.isNotEmpty) + buildRow( + title: 'یادداشت بازرسی', + value: item.reportInformation!.inspectionNotes ?? '-', + ), + + RElevated( + text: 'جزییات', + isFullWidth: true, + width: 150.w, + height: 40.h, + onPressed: () { + showDetailsBottomSheet(item); + }, + textStyle: AppFonts.yekan16.copyWith(color: Colors.white), + backgroundColor: AppColor.greenNormal, + ), + ], + ), + ); + } + + Row itemListWidgetReport(PoultryScienceReport item) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox(width: 20), + Expanded( + flex: 2, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 5, + children: [ + Text( + 'شناسه جوجه‌ریزی: ${item.hatching?.id ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + + Text( + item.createDate?.toJalali.formatCompactDate() ?? '-', + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + Expanded( + flex: 3, + child: Column( + spacing: 5, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'مرغداری: ${item.hatching?.poultry?.unitName ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + if (item.reportInformation?.inspectionStatus != null) + Text( + 'وضعیت بازرسی: ${item.reportInformation?.inspectionStatus ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + ], + ); + } + + void showDetailsBottomSheet(PoultryScienceReport item) { + Get.bottomSheet( + isScrollControlled: true, + BaseBottomSheet( + height: Get.height * 0.8, + rootChild: DetailsBottomSheetWidget(item: item), + ), + ); + } + + 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.getReport(); + Get.back(); + }, + height: 40, + ), + ], + ), + ); + } +} + +class DetailsBottomSheetWidget extends StatefulWidget { + final PoultryScienceReport item; + + const DetailsBottomSheetWidget({super.key, required this.item}); + + @override + State createState() => + _DetailsBottomSheetWidgetState(); +} + +class _DetailsBottomSheetWidgetState extends State { + int selectedTabIndex = 0; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 10, + ), + child: Text( + 'جزییات', + style: AppFonts.yekan18Bold.copyWith( + color: AppColor.iconColor, + ), + ), + ), + ], + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + tabBarWidget( + [ + 'اطلاعات کلی', + 'شرایط سالن', + 'تلفات', + 'کارشناس', + 'نهاده', + 'زیرساخت', + 'نیروی انسانی', + 'تسهیلات', + ], + selectedTabIndex, + (index) => setState(() => selectedTabIndex = index), + ), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _buildTableContent(), + ), + ), + ), + ], + ); + } + + Widget _buildTableContent() { + switch (selectedTabIndex) { + case 0: + return generalInfoTable(); + case 1: + return generalConditionHallTable(); + case 2: + return casualtiesTable(); + case 3: + return technicalOfficerTable(); + case 4: + return inputStatusTable(); + case 5: + return infrastructureEnergyTable(); + case 6: + return hrTable(); + case 7: + return facilitiesTable(); + default: + return generalInfoTable(); + } + } + + Widget technicalOfficerTable() { + final officer = widget.item.reportInformation?.technicalOfficer; + if (officer == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'کارشناس فنی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'کارشناس بهداشت', + value: officer.technicalHealthOfficer ?? '-', + ), + rTableRow( + title: 'کارشناس فنی', + value: officer.technicalEngineeringOfficer ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget generalInfoTable() { + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'اطلاعات کلی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت بازرسی', + value: + widget.item.reportInformation?.inspectionStatus ?? + widget.item.state ?? + '-', + ), + rTableRow( + title: 'یادداشت بازرسی', + value: widget.item.reportInformation?.inspectionNotes ?? '-', + ), + rTableRow(title: 'نقش', value: widget.item.reporterRole ?? '-'), + if (widget.item.lat != null && widget.item.log != null) + rTableRow( + title: 'موقعیت', + value: '${widget.item.lat}, ${widget.item.log}', + ), + if (widget.item.hatching?.id != null) + rTableRow( + title: 'شناسه جوجه ریزی', + value: widget.item.hatching!.id.toString(), + ), + ], + ), + ), + ], + ); + } + + Widget generalConditionHallTable() { + final hall = widget.item.reportInformation?.generalConditionHall; + if (hall == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'شرایط عمومی سالن', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow(title: 'وضعیت سلامت', value: hall.healthStatus ?? '-'), + rTableRow( + title: 'وضعیت تهویه', + value: hall.ventilationStatus ?? '-', + ), + rTableRow(title: 'وضعیت بستر', value: hall.bedCondition ?? '-'), + rTableRow(title: 'دما', value: hall.temperature ?? '-'), + rTableRow( + title: 'منبع آب آشامیدنی', + value: hall.drinkingWaterSource ?? '-', + ), + rTableRow( + title: 'کیفیت آب آشامیدنی', + value: hall.drinkingWaterQuality ?? '-', + ), + ], + ), + ), + if (hall.images != null && hall.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: hall.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(hall.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(hall.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget casualtiesTable() { + final casualties = widget.item.reportInformation?.casualties; + if (casualties == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تلفات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (casualties.normalLosses != null) + rTableRow( + title: 'تلفات عادی', + value: casualties.normalLosses.toString(), + ), + if (casualties.abnormalLosses != null) + rTableRow( + title: 'تلفات غیرعادی', + value: casualties.abnormalLosses.toString(), + ), + rTableRow( + title: 'منبع جوجه ریزی', + value: casualties.sourceOfHatching ?? '-', + ), + rTableRow( + title: 'علت تلفات غیرعادی', + value: casualties.causeAbnormalLosses ?? '-', + ), + rTableRow( + title: 'نوع بیماری', + value: casualties.typeDisease ?? '-', + ), + rTableRow( + title: 'نمونه‌برداری انجام شده', + value: casualties.samplingDone == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع نمونه‌برداری', + value: casualties.typeSampling ?? '-', + ), + ], + ), + ), + if (casualties.images != null && casualties.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: casualties.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(casualties.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(casualties.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget inputStatusTable() { + final inputStatus = widget.item.reportInformation?.inputStatus; + if (inputStatus == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'وضعیت نهاده', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت نهاده', + value: inputStatus.inputStatus ?? '-', + ), + rTableRow( + title: 'نام شرکت', + value: inputStatus.companyName ?? '-', + ), + rTableRow( + title: 'کد پیگیری', + value: inputStatus.trackingCode ?? '-', + ), + rTableRow( + title: 'نوع دانه', + value: inputStatus.typeOfGrain ?? '-', + ), + rTableRow( + title: 'موجودی در انبار', + value: inputStatus.inventoryInWarehouse ?? '-', + ), + rTableRow( + title: 'موجودی تا بازدید', + value: inputStatus.inventoryUntilVisit ?? '-', + ), + rTableRow( + title: 'درجه دانه', + value: inputStatus.gradeGrain ?? '-', + ), + ], + ), + ), + if (inputStatus.images != null && inputStatus.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: inputStatus.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(inputStatus.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(inputStatus.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget infrastructureEnergyTable() { + final infra = widget.item.reportInformation?.infrastructureEnergy; + if (infra == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'زیرساخت و انرژی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'نوع ژنراتور', + value: infra.generatorType ?? '-', + ), + rTableRow( + title: 'مدل ژنراتور', + value: infra.generatorModel ?? '-', + ), + rTableRow( + title: 'تعداد ژنراتور', + value: infra.generatorCount ?? '-', + ), + rTableRow( + title: 'ظرفیت ژنراتور', + value: infra.generatorCapacity ?? '-', + ), + rTableRow(title: 'نوع سوخت', value: infra.fuelType ?? '-'), + rTableRow( + title: 'عملکرد ژنراتور', + value: infra.generatorPerformance ?? '-', + ), + rTableRow( + title: 'موجودی سوخت اضطراری', + value: infra.emergencyFuelInventory ?? '-', + ), + rTableRow( + title: 'تاریخچه قطع برق', + value: infra.hasPowerCutHistory == true ? 'بله' : 'خیر', + ), + if (infra.hasPowerCutHistory == true) ...[ + rTableRow( + title: 'مدت قطع برق', + value: infra.powerCutDuration ?? '-', + ), + rTableRow( + title: 'ساعت قطع برق', + value: infra.powerCutHour ?? '-', + ), + ], + rTableRow( + title: 'یادداشت اضافی', + value: infra.additionalNotes ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget hrTable() { + final hr = widget.item.reportInformation?.hr; + if (hr == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'نیروی انسانی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (hr.numberEmployed != null) + rTableRow( + title: 'تعداد شاغلین', + value: hr.numberEmployed.toString(), + ), + if (hr.numberIndigenous != null) + rTableRow( + title: 'تعداد بومی', + value: hr.numberIndigenous.toString(), + ), + if (hr.numberNonIndigenous != null) + rTableRow( + title: 'تعداد غیربومی', + value: hr.numberNonIndigenous.toString(), + ), + rTableRow( + title: 'وضعیت قرارداد', + value: hr.contractStatus ?? '-', + ), + rTableRow( + title: 'آموزش دیده', + value: hr.trained == true ? 'بله' : 'خیر', + ), + ], + ), + ), + ], + ); + } + + Widget facilitiesTable() { + final facilities = widget.item.reportInformation?.facilities; + if (facilities == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تسهیلات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'دارای تسهیلات', + value: facilities.hasFacilities == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع تسهیلات', + value: facilities.typeOfFacility ?? '-', + ), + if (facilities.amount != null) + rTableRow(title: 'مبلغ', value: facilities.amount.toString()), + rTableRow(title: 'تاریخ', value: facilities.date ?? '-'), + rTableRow( + title: 'وضعیت بازپرداخت', + value: facilities.repaymentStatus ?? '-', + ), + rTableRow( + title: 'درخواست تسهیلات', + value: facilities.requestFacilities ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget tabBarWidget( + List tabs, + int selectedIndex, + Function(int) onTabSelected, + ) { + return SizedBox( + height: 40.h, + width: Get.width, + child: Column( + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + reverse: true, + child: Row( + children: [ + ...tabs.map( + (tab) => GestureDetector( + onTap: () => onTabSelected(tabs.indexOf(tab)), + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 11, + ), + decoration: BoxDecoration( + border: tab == tabs[selectedIndex] + ? Border( + bottom: BorderSide( + color: AppColor.blueNormalOld, + width: 3, + ), + ) + : null, + ), + child: Text( + tab, + style: AppFonts.yekan12Bold.copyWith( + color: tab == tabs[selectedIndex] + ? AppColor.blueNormalOld + : AppColor.mediumGrey, + ), + ), + ), + ), + ), + ], + ), + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + ], + ), + ); + } + + Row rTableRow({String? title, String? value}) { + return Row( + children: [ + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + color: AppColor.bgLight2, + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + title ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.iconColor), + ), + ), + ), + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + value ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + ), + ), + ], + ); + } + + void showImageDialog(List images, int initialIndex) { + final pageController = PageController(initialPage: initialIndex); + final currentIndex = initialIndex.obs; + + Get.dialog( + Dialog( + backgroundColor: Colors.transparent, + insetPadding: EdgeInsets.zero, + child: Container( + width: Get.width, + height: Get.height, + color: Colors.black, + child: Stack( + children: [ + PageView.builder( + controller: pageController, + itemCount: images.length, + onPageChanged: (index) { + currentIndex.value = index; + }, + itemBuilder: (context, index) { + return InteractiveViewer( + minScale: 0.5, + maxScale: 4.0, + child: Center( + child: Image.network( + images[index], + fit: BoxFit.contain, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + color: Colors.white, + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Center( + child: Icon( + Icons.error, + color: Colors.white, + size: 50, + ), + ); + }, + ), + ), + ); + }, + ), + Positioned( + top: 40, + right: 16, + child: IconButton( + icon: Icon(Icons.close, color: Colors.white, size: 30), + onPressed: () => Get.back(), + ), + ), + if (images.length > 1) + Positioned( + bottom: 20, + left: 0, + right: 0, + child: Center( + child: Obx( + () => Container( + padding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '${currentIndex.value + 1} / ${images.length}', + style: TextStyle(color: Colors.white, fontSize: 14), + ), + ), + ), + ), + ), + ], + ), + ), + ), + barrierDismissible: true, + ); + } +} diff --git a/packages/chicken/lib/features/city_jahad/presentation/pages/root/logic.dart b/packages/chicken/lib/features/city_jahad/presentation/pages/root/logic.dart new file mode 100644 index 0000000..6117f02 --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/presentation/pages/root/logic.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/city_jahad/data/repositories/city_jahad_repository.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/routes/pages.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/pages/home/view.dart'; +import 'package:rasadyar_chicken/features/common/presentation/page/profile/view.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/utils/utils.dart'; +import 'package:rasadyar_core/core.dart'; + +enum ErrorLocationType { serviceDisabled, permissionDenied, none } + +class CityJahadRootLogic extends GetxController { + var tokenService = Get.find(); + + late CityJahadRepository cityJahadRepository; + + RxList errorLocationType = RxList(); + RxMap homeExpandedList = RxMap(); + DateTime? _lastBackPressed; + + RxInt currentPage = 0.obs; + + final pages = [ + Navigator( + key: Get.nestedKey(cityJahadActionKey), + onGenerateRoute: (settings) { + final page = CityJahadPages.pages.firstWhere( + (e) => e.name == settings.name, + orElse: () => CityJahadPages.pages.firstWhere( + (e) => e.name == CityJahadRoutes.homeCityJahad, + ), + ); + + return buildRouteFromGetPage(page); + }, + ), + + ProfilePage(), + ]; + + @override + void onInit() { + super.onInit(); + cityJahadRepository = diChicken.get(); + } + + void toggleExpanded(int index) { + if (homeExpandedList.keys.contains(index)) { + homeExpandedList.remove(index); + } else { + homeExpandedList[index] = false; + } + } + + void rootErrorHandler(DioException error) { + handleGeneric(error, () { + tokenService.deleteModuleTokens(Module.chicken); + }); + } + + void changePage(int index) { + currentPage.value = index; + } + + void popBackTaped() async { + 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(); + } + } +} diff --git a/packages/chicken/lib/features/city_jahad/presentation/pages/root/view.dart b/packages/chicken/lib/features/city_jahad/presentation/pages/root/view.dart new file mode 100644 index 0000000..d2753c1 --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/presentation/pages/root/view.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class CityJahadRootPage extends GetView { + const CityJahadRootPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isFullScreen: true, + onPopScopTaped: controller.popBackTaped, + child: ObxValue((data) { + return Stack( + children: [ + IndexedStack(children: controller.pages, index: data.value), + Positioned( + right: 0, + left: 0, + bottom: 0, + child: RBottomNavigation( + mainAxisAlignment: MainAxisAlignment.spaceAround, + items: [ + + RBottomNavigationItem( + label: 'خانه', + icon: Assets.vec.homeSvg.path, + isSelected: controller.currentPage.value == 0, + onTap: () { + Get.nestedKey( + cityJahadActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(0); + }, + ), + RBottomNavigationItem( + label: 'پروفایل', + icon: Assets.vec.profileCircleSvg.path, + isSelected: controller.currentPage.value == 1, + onTap: () { + Get.nestedKey( + cityJahadActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(1); + }, + ), + ], + ), + ), + ], + ); + }, controller.currentPage), + ); + } +} diff --git a/packages/chicken/lib/features/city_jahad/presentation/routes/pages.dart b/packages/chicken/lib/features/city_jahad/presentation/routes/pages.dart new file mode 100644 index 0000000..8035307 --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/presentation/routes/pages.dart @@ -0,0 +1,66 @@ +import 'package:rasadyar_chicken/features/city_jahad/presentation/pages/home/logic.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/pages/home/view.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/pages/root/logic.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/pages/root/view.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/pages/active_hatching/view.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/pages/new_inspection/logic.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/pages/new_inspection/view.dart'; +import 'package:rasadyar_chicken/features/city_jahad/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet_logic.dart'; +import 'package:rasadyar_chicken/presentation/routes/global_binding.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class CityJahadPages { + CityJahadPages._(); + + static List get pages => [ + GetPage( + name: CityJahadRoutes.initCityJahad, + page: () => CityJahadRootPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ChickenBaseLogic(), fenix: true); + Get.lazyPut(() => CityJahadRootLogic()); + Get.lazyPut(() => CityJahadHomeLogic()); + }), + ], + ), + GetPage( + name: CityJahadRoutes.homeCityJahad, + page: () => CityJahadHomePage(), + middlewares: [AuthMiddleware()], + binding: BindingsBuilder(() { + Get.put(CityJahadHomeLogic()); + Get.lazyPut(() => ChickenBaseLogic()); + }), + ), + + GetPage( + name: CityJahadRoutes.activeHatchingCityJahad, + page: () => ActiveHatchingPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ActiveHatchingLogic()); + Get.lazyPut(() => CreateInspectionBottomSheetLogic()); + }), + ], + ), + GetPage( + name: CityJahadRoutes.newInspectionCityJahad, + page: () => NewInspectionPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => NewInspectionLogic()); + }), + ], + ), + ]; +} diff --git a/packages/chicken/lib/features/city_jahad/presentation/routes/routes.dart b/packages/chicken/lib/features/city_jahad/presentation/routes/routes.dart new file mode 100644 index 0000000..5415f55 --- /dev/null +++ b/packages/chicken/lib/features/city_jahad/presentation/routes/routes.dart @@ -0,0 +1,10 @@ +sealed class CityJahadRoutes { + CityJahadRoutes._(); + + static const _base = '/chicken/cityJahad'; + static const initCityJahad = '$_base/'; + static const homeCityJahad = '$_base/home'; + static const actionCityJahad = '$_base/action'; + static const activeHatchingCityJahad = '$_base/activeHatching'; + static const newInspectionCityJahad = '$_base/newInspection'; +} diff --git a/packages/chicken/lib/features/common/presentation/page/auth/logic.dart b/packages/chicken/lib/features/common/presentation/page/auth/logic.dart index 6577ddf..3dba1b9 100644 --- a/packages/chicken/lib/features/common/presentation/page/auth/logic.dart +++ b/packages/chicken/lib/features/common/presentation/page/auth/logic.dart @@ -135,7 +135,18 @@ class AuthLogic extends GetxController with GetTickerProviderStateMixin { result?.accessToken ?? '', ); var tmpRoles = result?.role?.where((element) { - final allowedRoles = {'poultryscience', 'steward', 'killhouse'}; + final allowedRoles = { + 'poultryscience', + 'steward', + 'killhouse', + 'provinceinspector', + 'cityjahad', + 'jahad', + 'vetfarm', + 'provincesupervisor', + 'superadmin', + }; + final lowerElement = element.toString().toLowerCase().trim(); return allowedRoles.contains(lowerElement); }).toList(); diff --git a/packages/chicken/lib/features/jahad/data/datasources/remote/jahad_remote_data_source.dart b/packages/chicken/lib/features/jahad/data/datasources/remote/jahad_remote_data_source.dart new file mode 100644 index 0000000..8499d17 --- /dev/null +++ b/packages/chicken/lib/features/jahad/data/datasources/remote/jahad_remote_data_source.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class JahadRemoteDataSource { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/jahad/data/datasources/remote/jahad_remote_data_source_impl.dart b/packages/chicken/lib/features/jahad/data/datasources/remote/jahad_remote_data_source_impl.dart new file mode 100644 index 0000000..d94513c --- /dev/null +++ b/packages/chicken/lib/features/jahad/data/datasources/remote/jahad_remote_data_source_impl.dart @@ -0,0 +1,39 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/jahad/data/datasources/remote/jahad_remote_data_source.dart'; +import 'package:rasadyar_core/core.dart'; + +class JahadRemoteDataSourceImpl implements JahadRemoteDataSource { + final DioRemote _httpClient; + + JahadRemoteDataSourceImpl(this._httpClient); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + var res = await _httpClient.get( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + queryParameters: queryParameters, + fromJson: (json) => PaginationModel.fromJson( + json, + (json) => PoultryScienceReport.fromJson(json as Map), + ), + ); + return res.data; + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + await _httpClient.post( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + data: request.toJson(), + ); + } +} diff --git a/packages/chicken/lib/features/jahad/data/di/jahad_di.dart b/packages/chicken/lib/features/jahad/data/di/jahad_di.dart new file mode 100644 index 0000000..3a935d2 --- /dev/null +++ b/packages/chicken/lib/features/jahad/data/di/jahad_di.dart @@ -0,0 +1,36 @@ +import 'package:rasadyar_chicken/features/jahad/data/datasources/remote/jahad_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/jahad/data/datasources/remote/jahad_remote_data_source_impl.dart'; +import 'package:rasadyar_chicken/features/jahad/data/repositories/jahad_repository.dart'; +import 'package:rasadyar_chicken/features/jahad/data/repositories/jahad_repository_impl.dart'; +import 'package:rasadyar_core/core.dart'; + +/// Setup dependency injection for jahad feature +Future setupJahadDI(GetIt di, DioRemote dioRemote) async { + di.registerLazySingleton( + () => JahadRemoteDataSourceImpl(dioRemote), + ); + + di.registerLazySingleton( + () => JahadRepositoryImpl(di.get()), + ); +} + +/// Re-register jahad dependencies (used when base URL changes) +Future reRegisterJahadDI(GetIt di, DioRemote dioRemote) async { + await reRegister(di, () => JahadRemoteDataSourceImpl(dioRemote)); + await reRegister( + di, + () => JahadRepositoryImpl(di.get()), + ); +} + +/// Helper function to re-register a dependency +Future reRegister( + GetIt di, + T Function() factory, +) async { + if (di.isRegistered()) { + await di.unregister(); + } + di.registerLazySingleton(factory); +} diff --git a/packages/chicken/lib/features/jahad/data/repositories/jahad_repository.dart b/packages/chicken/lib/features/jahad/data/repositories/jahad_repository.dart new file mode 100644 index 0000000..84c763a --- /dev/null +++ b/packages/chicken/lib/features/jahad/data/repositories/jahad_repository.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class JahadRepository { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/jahad/data/repositories/jahad_repository_impl.dart b/packages/chicken/lib/features/jahad/data/repositories/jahad_repository_impl.dart new file mode 100644 index 0000000..4c52ffc --- /dev/null +++ b/packages/chicken/lib/features/jahad/data/repositories/jahad_repository_impl.dart @@ -0,0 +1,30 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/jahad/data/datasources/remote/jahad_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/jahad/data/repositories/jahad_repository.dart'; +import 'package:rasadyar_core/core.dart'; + +class JahadRepositoryImpl implements JahadRepository { + final JahadRemoteDataSource _remote; + + JahadRepositoryImpl(this._remote); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + return await _remote.getSubmitInspectionList( + token: token, + queryParameters: queryParameters, + ); + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + return await _remote.submitInspection(token: token, request: request); + } +} diff --git a/packages/chicken/lib/features/jahad/jahad.dart b/packages/chicken/lib/features/jahad/jahad.dart new file mode 100644 index 0000000..d0d033a --- /dev/null +++ b/packages/chicken/lib/features/jahad/jahad.dart @@ -0,0 +1,3 @@ +export 'data/di/jahad_di.dart'; +export 'presentation/routes/routes.dart'; +export 'presentation/routes/pages.dart'; diff --git a/packages/chicken/lib/features/jahad/presentation/pages/active_hatching/logic.dart b/packages/chicken/lib/features/jahad/presentation/pages/active_hatching/logic.dart new file mode 100644 index 0000000..e1ef389 --- /dev/null +++ b/packages/chicken/lib/features/jahad/presentation/pages/active_hatching/logic.dart @@ -0,0 +1,95 @@ +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/pages/root/logic.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/repositories/poultry_science_repository.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingLogic extends GetxController { + JahadRootLogic rootLogic = Get.find(); + BaseLogic baseLogic = Get.find(); + late PoultryScienceRepository poultryScienceRepository; + Rx>> activeHatchingList = + Resource>.loading().obs; + + final RxBool isLoadingMoreList = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + List routesName = ['اقدام', 'جوجه ریزی فعال']; + + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + RxnString searchedValue = RxnString(); + + @override + void onInit() { + super.onInit(); + poultryScienceRepository = diChicken.get(); + } + + @override + void onReady() { + super.onReady(); + getHatchingList(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getHatchingList([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreList.value = true; + } else { + activeHatchingList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => await poultryScienceRepository.getHatchingPoultry( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + queryParams: {'type': 'hatching'}, + role: 'Jahad', + pageSize: 50, + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + activeHatchingList.value = + Resource>.empty(); + } else { + activeHatchingList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(activeHatchingList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + Future onRefresh() async { + currentPage.value = 1; + await getHatchingList(); + } +} diff --git a/packages/chicken/lib/features/jahad/presentation/pages/active_hatching/view.dart b/packages/chicken/lib/features/jahad/presentation/pages/active_hatching/view.dart new file mode 100644 index 0000000..760688d --- /dev/null +++ b/packages/chicken/lib/features/jahad/presentation/pages/active_hatching/view.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet_logic.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingPage extends GetView { + const ActiveHatchingPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasSearch: true, + hasFilter: false, + backId: jahadActionKey, + routes: controller.routesName, + onSearchChanged: (data) { + controller.searchedValue.value = data; + controller.getHatchingList(); + }, + child: hatchingWidget(), + /*widgets: [ + hatchingWidget() + ],*/ + ); + } + + Widget hatchingWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidget(item), + secondChild: itemListExpandedWidget(item), + labelColor: AppColor.blueLight, + labelIcon: Assets.vec.activeFramSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getHatchingList(true), + ); + }, controller.activeHatchingList); + } + + Container itemListExpandedWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.center, + style: AppFonts.yekan16.copyWith(color: AppColor.greenDark), + ), + Spacer(), + + Visibility( + child: Text( + item.violation == true ? 'پیگیری' : 'عادی', + textAlign: TextAlign.center, + style: AppFonts.yekan10.copyWith(color: AppColor.redDark), + ), + ), + ], + ), + 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: [ + Text( + 'نژاد:${item.breed?.first.breed ?? 'N/A'}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + Text( + ' سن${item.age} (روز)', + + style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal), + ), + Text( + ' دوره جوجه ریزی:${item.period}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ], + ), + ), + + buildRow( + title: 'شماره مجوز جوجه ریزی', + value: item.licenceNumber ?? 'N/A', + ), + buildUnitRow( + title: 'حجم جوجه ریزی', + value: item.quantity.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'مانده در سالن', + value: item.leftOver.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'تلفات', + value: item.losses.separatedByCommaFa, + unit: '(قطعه)', + ), + buildRow( + title: 'دامپزشک فارم', + value: + '${item.vetFarm?.vetFarmFullName}(${item.vetFarm?.vetFarmMobile})', + ), + buildRow( + title: 'شرح بازرسی', + value: item.reportInfo?.image == false + ? 'ارسال تصویر جوجه ریزی فارم ' + : 'تکمیل شده', + titleStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + valueStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + ), + + RElevated( + height: 40.h, + isFullWidth: true, + onPressed: () { + Get.find().setHatchingModel( + item, + ); + Get.bottomSheet( + CreateInspectionBottomSheet(), + isScrollControlled: true, + ignoreSafeArea: false, + ).then((value) { + if (Get.isRegistered()) { + Get.find().clearForm(); + } + }); + }, + child: Text('ثبت بازرسی'), + ), + ], + ), + ); + } + + Widget itemListWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + Text( + item.poultry?.user?.mobile ?? '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: [ + Text( + item.poultry?.unitName ?? 'N/Aaq', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.bgDark), + ), + Text( + item.poultry?.licenceNumber ?? 'N/A', + textAlign: TextAlign.left, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + ], + ), + ), + Expanded( + flex: 1, + child: Assets.vec.scanSvg.svg( + width: 32.w, + height: 32.h, + colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn), + ), + ), + ], + ); + } +} diff --git a/packages/chicken/lib/features/jahad/presentation/pages/home/logic.dart b/packages/chicken/lib/features/jahad/presentation/pages/home/logic.dart new file mode 100644 index 0000000..f8d9114 --- /dev/null +++ b/packages/chicken/lib/features/jahad/presentation/pages/home/logic.dart @@ -0,0 +1,29 @@ +import 'package:rasadyar_chicken/features/jahad/presentation/routes/routes.dart'; +import 'package:rasadyar_core/core.dart'; + +class JahadActionItem { + final String title; + final String route; + final String icon; + + JahadActionItem({ + required this.title, + required this.route, + required this.icon, + }); +} + +class JahadHomeLogic extends GetxController { + RxList items = [ + JahadActionItem( + title: "جوجه ریزی فعال", + route: JahadRoutes.activeHatchingJahad, + icon: Assets.vec.activeFramSvg.path, + ), + JahadActionItem( + title: "بازرسی مزارع طیور", + route: JahadRoutes.newInspectionJahad, + icon: Assets.vec.activeFramSvg.path, + ), + ].obs; +} diff --git a/packages/chicken/lib/features/jahad/presentation/pages/home/view.dart b/packages/chicken/lib/features/jahad/presentation/pages/home/view.dart new file mode 100644 index 0000000..0c042de --- /dev/null +++ b/packages/chicken/lib/features/jahad/presentation/pages/home/view.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class JahadHomePage extends GetView { + JahadHomePage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isBase: true, + hasNews: true, + hasNotification: true, + child: gridWidget(), + ); + } + + Widget gridWidget() { + return ObxValue((data) { + return GridView.builder( + physics: BouncingScrollPhysics(), + padding: EdgeInsets.symmetric(vertical: 18.h, horizontal: 32.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 24.h, + crossAxisSpacing: 24.w, + ), + itemCount: data.length, + hitTestBehavior: HitTestBehavior.opaque, + itemBuilder: (BuildContext context, int index) { + var item = data[index]; + return GlassMorphismCardIcon( + title: item.title, + vecIcon: item.icon, + onTap: () async { + Get.toNamed(item.route, id: jahadActionKey); + }, + ); + }, + ); + }, controller.items); + } +} diff --git a/packages/chicken/lib/features/jahad/presentation/pages/new_inspection/logic.dart b/packages/chicken/lib/features/jahad/presentation/pages/new_inspection/logic.dart new file mode 100644 index 0000000..17f6200 --- /dev/null +++ b/packages/chicken/lib/features/jahad/presentation/pages/new_inspection/logic.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/pages/root/logic.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class NewInspectionLogic extends GetxController { + BaseLogic baseLogic = Get.find(); + + Rx>> submitInspectionList = + Resource>.loading().obs; + + JahadRootLogic rootLogic = Get.find(); + + final RxBool isLoadingMoreAllocationsMade = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + RxList pickedImages = [].obs; + final List _multiPartPickedImages = []; + + RxBool isOnUpload = false.obs; + + RxDouble presentUpload = 0.0.obs; + RxList routesName = RxList(); + RxInt selectedSegmentIndex = 0.obs; + + RxnString searchedValue = RxnString(); + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + + @override + void onInit() { + super.onInit(); + + routesName.value = ['اقدام'].toList(); + + ever(selectedSegmentIndex, (callback) { + routesName.removeLast(); + routesName.add(callback == 0 ? 'بازرسی' : 'بایگانی'); + }); + } + + @override + void onReady() { + super.onReady(); + + getReport(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getReport([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreAllocationsMade.value = true; + } else { + submitInspectionList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => await rootLogic.jahadRepository.getSubmitInspectionList( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + role: 'Jahad', + pageSize: 50, + search: 'filter', + + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + submitInspectionList.value = + Resource>.empty(); + } else { + submitInspectionList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(submitInspectionList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + Future pickImages() async { + determineCurrentPosition(); + var tmp = await pickCameraImage(); + if (tmp?.path != null && pickedImages.length < 7) { + pickedImages.add(tmp!); + } + } + + void removeImage(int index) { + pickedImages.removeAt(index); + } + + void closeBottomSheet() { + Get.back(); + } + + double calculateUploadProgress({required int sent, required int total}) { + if (total != 0) { + double progress = (sent * 100 / total) / 100; + return progress; + } else { + return 0.0; + } + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + void setSearchValue(String? data) { + dLog('Search Value: $data'); + searchedValue.value = data?.trim(); + getReport(); + } + + Future onRefresh() async { + currentPage.value = 1; + await getReport(); + } + + String getStatus(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return 'در حال بررسی'; + } + return status; + } + + Color getStatusColor(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return AppColor.yellowNormal; + } + // می‌توانید منطق رنگ را بر اساس inspectionStatus تنظیم کنید + return AppColor.greenNormal; + } +} diff --git a/packages/chicken/lib/features/jahad/presentation/pages/new_inspection/view.dart b/packages/chicken/lib/features/jahad/presentation/pages/new_inspection/view.dart new file mode 100644 index 0000000..8c494d3 --- /dev/null +++ b/packages/chicken/lib/features/jahad/presentation/pages/new_inspection/view.dart @@ -0,0 +1,1134 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class NewInspectionPage extends GetView { + const NewInspectionPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasBack: true, + hasFilter: true, + hasSearch: true, + onFilterTap: () { + Get.bottomSheet(filterBottomSheet()); + }, + onRefresh: controller.onRefresh, + onSearchChanged: (data) => controller.setSearchValue(data), + backId: jahadActionKey, + routesWidget: ContainerBreadcrumb(rxRoutes: controller.routesName), + child: Column(children: [reportWidget()]), + ); + } + + Widget reportWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidgetReport(item), + secondChild: itemListExpandedWidgetReport(item), + labelColor: AppColor.greenLight, + labelIcon: Assets.vec.cubeSearchSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getReport(true), + ); + }, controller.submitInspectionList), + ); + } + + Widget itemListExpandedWidgetReport(PoultryScienceReport item) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + spacing: 8, + children: [ + buildRow( + title: 'وضعیت بازرسی', + value: controller.getStatus(item), + titleStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + valueStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + ), + if (item.hatching?.poultry?.unitName != null) + buildRow( + title: 'مرغداری', + value: item.hatching?.poultry?.unitName ?? '-', + ), + if (item.hatching?.id != null) + buildRow( + title: 'شناسه جوجه‌ریزی', + value: item.hatching!.id.toString(), + ), + + if (item + .reportInformation + ?.technicalOfficer + ?.technicalHealthOfficer != + null) + buildRow( + title: 'کارشناس بهداشت', + value: + item + .reportInformation! + .technicalOfficer! + .technicalHealthOfficer ?? + '-', + ), + if (item + .reportInformation + ?.technicalOfficer + ?.technicalEngineeringOfficer != + null) + buildRow( + title: 'کارشناس فنی', + value: + item + .reportInformation! + .technicalOfficer! + .technicalEngineeringOfficer ?? + '-', + ), + if (item.reportInformation?.casualties?.normalLosses != null || + item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات عادی', + value: (item.reportInformation?.casualties?.normalLosses ?? 0) + .toString(), + unit: '(قطعه)', + ), + if (item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات غیرعادی', + value: item.reportInformation!.casualties!.abnormalLosses + .toString(), + unit: '(قطعه)', + ), + + if (item.reportInformation?.inspectionNotes != null && + item.reportInformation!.inspectionNotes!.isNotEmpty) + buildRow( + title: 'یادداشت بازرسی', + value: item.reportInformation!.inspectionNotes ?? '-', + ), + + RElevated( + text: 'جزییات', + isFullWidth: true, + width: 150.w, + height: 40.h, + onPressed: () { + showDetailsBottomSheet(item); + }, + textStyle: AppFonts.yekan16.copyWith(color: Colors.white), + backgroundColor: AppColor.greenNormal, + ), + ], + ), + ); + } + + Row itemListWidgetReport(PoultryScienceReport item) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox(width: 20), + Expanded( + flex: 2, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 5, + children: [ + Text( + 'شناسه جوجه‌ریزی: ${item.hatching?.id ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + + Text( + item.createDate?.toJalali.formatCompactDate() ?? '-', + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + Expanded( + flex: 3, + child: Column( + spacing: 5, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'مرغداری: ${item.hatching?.poultry?.unitName ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + if (item.reportInformation?.inspectionStatus != null) + Text( + 'وضعیت بازرسی: ${item.reportInformation?.inspectionStatus ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + ], + ); + } + + void showDetailsBottomSheet(PoultryScienceReport item) { + Get.bottomSheet( + isScrollControlled: true, + BaseBottomSheet( + height: Get.height * 0.8, + rootChild: DetailsBottomSheetWidget(item: item), + ), + ); + } + + 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.getReport(); + Get.back(); + }, + height: 40, + ), + ], + ), + ); + } +} + +class DetailsBottomSheetWidget extends StatefulWidget { + final PoultryScienceReport item; + + const DetailsBottomSheetWidget({super.key, required this.item}); + + @override + State createState() => + _DetailsBottomSheetWidgetState(); +} + +class _DetailsBottomSheetWidgetState extends State { + int selectedTabIndex = 0; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 10, + ), + child: Text( + 'جزییات', + style: AppFonts.yekan18Bold.copyWith( + color: AppColor.iconColor, + ), + ), + ), + ], + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + tabBarWidget( + [ + 'اطلاعات کلی', + 'شرایط سالن', + 'تلفات', + 'کارشناس', + 'نهاده', + 'زیرساخت', + 'نیروی انسانی', + 'تسهیلات', + ], + selectedTabIndex, + (index) => setState(() => selectedTabIndex = index), + ), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _buildTableContent(), + ), + ), + ), + ], + ); + } + + Widget _buildTableContent() { + switch (selectedTabIndex) { + case 0: + return generalInfoTable(); + case 1: + return generalConditionHallTable(); + case 2: + return casualtiesTable(); + case 3: + return technicalOfficerTable(); + case 4: + return inputStatusTable(); + case 5: + return infrastructureEnergyTable(); + case 6: + return hrTable(); + case 7: + return facilitiesTable(); + default: + return generalInfoTable(); + } + } + + Widget technicalOfficerTable() { + final officer = widget.item.reportInformation?.technicalOfficer; + if (officer == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'کارشناس فنی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'کارشناس بهداشت', + value: officer.technicalHealthOfficer ?? '-', + ), + rTableRow( + title: 'کارشناس فنی', + value: officer.technicalEngineeringOfficer ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget generalInfoTable() { + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'اطلاعات کلی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت بازرسی', + value: + widget.item.reportInformation?.inspectionStatus ?? + widget.item.state ?? + '-', + ), + rTableRow( + title: 'یادداشت بازرسی', + value: widget.item.reportInformation?.inspectionNotes ?? '-', + ), + rTableRow(title: 'نقش', value: widget.item.reporterRole ?? '-'), + if (widget.item.lat != null && widget.item.log != null) + rTableRow( + title: 'موقعیت', + value: '${widget.item.lat}, ${widget.item.log}', + ), + if (widget.item.hatching?.id != null) + rTableRow( + title: 'شناسه جوجه ریزی', + value: widget.item.hatching!.id.toString(), + ), + ], + ), + ), + ], + ); + } + + Widget generalConditionHallTable() { + final hall = widget.item.reportInformation?.generalConditionHall; + if (hall == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'شرایط عمومی سالن', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow(title: 'وضعیت سلامت', value: hall.healthStatus ?? '-'), + rTableRow( + title: 'وضعیت تهویه', + value: hall.ventilationStatus ?? '-', + ), + rTableRow(title: 'وضعیت بستر', value: hall.bedCondition ?? '-'), + rTableRow(title: 'دما', value: hall.temperature ?? '-'), + rTableRow( + title: 'منبع آب آشامیدنی', + value: hall.drinkingWaterSource ?? '-', + ), + rTableRow( + title: 'کیفیت آب آشامیدنی', + value: hall.drinkingWaterQuality ?? '-', + ), + ], + ), + ), + if (hall.images != null && hall.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: hall.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(hall.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(hall.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget casualtiesTable() { + final casualties = widget.item.reportInformation?.casualties; + if (casualties == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تلفات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (casualties.normalLosses != null) + rTableRow( + title: 'تلفات عادی', + value: casualties.normalLosses.toString(), + ), + if (casualties.abnormalLosses != null) + rTableRow( + title: 'تلفات غیرعادی', + value: casualties.abnormalLosses.toString(), + ), + rTableRow( + title: 'منبع جوجه ریزی', + value: casualties.sourceOfHatching ?? '-', + ), + rTableRow( + title: 'علت تلفات غیرعادی', + value: casualties.causeAbnormalLosses ?? '-', + ), + rTableRow( + title: 'نوع بیماری', + value: casualties.typeDisease ?? '-', + ), + rTableRow( + title: 'نمونه‌برداری انجام شده', + value: casualties.samplingDone == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع نمونه‌برداری', + value: casualties.typeSampling ?? '-', + ), + ], + ), + ), + if (casualties.images != null && casualties.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: casualties.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(casualties.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(casualties.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget inputStatusTable() { + final inputStatus = widget.item.reportInformation?.inputStatus; + if (inputStatus == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'وضعیت نهاده', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت نهاده', + value: inputStatus.inputStatus ?? '-', + ), + rTableRow( + title: 'نام شرکت', + value: inputStatus.companyName ?? '-', + ), + rTableRow( + title: 'کد پیگیری', + value: inputStatus.trackingCode ?? '-', + ), + rTableRow( + title: 'نوع دانه', + value: inputStatus.typeOfGrain ?? '-', + ), + rTableRow( + title: 'موجودی در انبار', + value: inputStatus.inventoryInWarehouse ?? '-', + ), + rTableRow( + title: 'موجودی تا بازدید', + value: inputStatus.inventoryUntilVisit ?? '-', + ), + rTableRow( + title: 'درجه دانه', + value: inputStatus.gradeGrain ?? '-', + ), + ], + ), + ), + if (inputStatus.images != null && inputStatus.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: inputStatus.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(inputStatus.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(inputStatus.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget infrastructureEnergyTable() { + final infra = widget.item.reportInformation?.infrastructureEnergy; + if (infra == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'زیرساخت و انرژی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'نوع ژنراتور', + value: infra.generatorType ?? '-', + ), + rTableRow( + title: 'مدل ژنراتور', + value: infra.generatorModel ?? '-', + ), + rTableRow( + title: 'تعداد ژنراتور', + value: infra.generatorCount ?? '-', + ), + rTableRow( + title: 'ظرفیت ژنراتور', + value: infra.generatorCapacity ?? '-', + ), + rTableRow(title: 'نوع سوخت', value: infra.fuelType ?? '-'), + rTableRow( + title: 'عملکرد ژنراتور', + value: infra.generatorPerformance ?? '-', + ), + rTableRow( + title: 'موجودی سوخت اضطراری', + value: infra.emergencyFuelInventory ?? '-', + ), + rTableRow( + title: 'تاریخچه قطع برق', + value: infra.hasPowerCutHistory == true ? 'بله' : 'خیر', + ), + if (infra.hasPowerCutHistory == true) ...[ + rTableRow( + title: 'مدت قطع برق', + value: infra.powerCutDuration ?? '-', + ), + rTableRow( + title: 'ساعت قطع برق', + value: infra.powerCutHour ?? '-', + ), + ], + rTableRow( + title: 'یادداشت اضافی', + value: infra.additionalNotes ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget hrTable() { + final hr = widget.item.reportInformation?.hr; + if (hr == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'نیروی انسانی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (hr.numberEmployed != null) + rTableRow( + title: 'تعداد شاغلین', + value: hr.numberEmployed.toString(), + ), + if (hr.numberIndigenous != null) + rTableRow( + title: 'تعداد بومی', + value: hr.numberIndigenous.toString(), + ), + if (hr.numberNonIndigenous != null) + rTableRow( + title: 'تعداد غیربومی', + value: hr.numberNonIndigenous.toString(), + ), + rTableRow( + title: 'وضعیت قرارداد', + value: hr.contractStatus ?? '-', + ), + rTableRow( + title: 'آموزش دیده', + value: hr.trained == true ? 'بله' : 'خیر', + ), + ], + ), + ), + ], + ); + } + + Widget facilitiesTable() { + final facilities = widget.item.reportInformation?.facilities; + if (facilities == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تسهیلات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'دارای تسهیلات', + value: facilities.hasFacilities == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع تسهیلات', + value: facilities.typeOfFacility ?? '-', + ), + if (facilities.amount != null) + rTableRow(title: 'مبلغ', value: facilities.amount.toString()), + rTableRow(title: 'تاریخ', value: facilities.date ?? '-'), + rTableRow( + title: 'وضعیت بازپرداخت', + value: facilities.repaymentStatus ?? '-', + ), + rTableRow( + title: 'درخواست تسهیلات', + value: facilities.requestFacilities ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget tabBarWidget( + List tabs, + int selectedIndex, + Function(int) onTabSelected, + ) { + return SizedBox( + height: 40.h, + width: Get.width, + child: Column( + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + reverse: true, + child: Row( + children: [ + ...tabs.map( + (tab) => GestureDetector( + onTap: () => onTabSelected(tabs.indexOf(tab)), + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 11, + ), + decoration: BoxDecoration( + border: tab == tabs[selectedIndex] + ? Border( + bottom: BorderSide( + color: AppColor.blueNormalOld, + width: 3, + ), + ) + : null, + ), + child: Text( + tab, + style: AppFonts.yekan12Bold.copyWith( + color: tab == tabs[selectedIndex] + ? AppColor.blueNormalOld + : AppColor.mediumGrey, + ), + ), + ), + ), + ), + ], + ), + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + ], + ), + ); + } + + Row rTableRow({String? title, String? value}) { + return Row( + children: [ + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + color: AppColor.bgLight2, + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + title ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.iconColor), + ), + ), + ), + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + value ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + ), + ), + ], + ); + } + + void showImageDialog(List images, int initialIndex) { + final pageController = PageController(initialPage: initialIndex); + final currentIndex = initialIndex.obs; + + Get.dialog( + Dialog( + backgroundColor: Colors.transparent, + insetPadding: EdgeInsets.zero, + child: Container( + width: Get.width, + height: Get.height, + color: Colors.black, + child: Stack( + children: [ + PageView.builder( + controller: pageController, + itemCount: images.length, + onPageChanged: (index) { + currentIndex.value = index; + }, + itemBuilder: (context, index) { + return InteractiveViewer( + minScale: 0.5, + maxScale: 4.0, + child: Center( + child: Image.network( + images[index], + fit: BoxFit.contain, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + color: Colors.white, + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Center( + child: Icon( + Icons.error, + color: Colors.white, + size: 50, + ), + ); + }, + ), + ), + ); + }, + ), + Positioned( + top: 40, + right: 16, + child: IconButton( + icon: Icon(Icons.close, color: Colors.white, size: 30), + onPressed: () => Get.back(), + ), + ), + if (images.length > 1) + Positioned( + bottom: 20, + left: 0, + right: 0, + child: Center( + child: Obx( + () => Container( + padding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '${currentIndex.value + 1} / ${images.length}', + style: TextStyle(color: Colors.white, fontSize: 14), + ), + ), + ), + ), + ), + ], + ), + ), + ), + barrierDismissible: true, + ); + } +} diff --git a/packages/chicken/lib/features/jahad/presentation/pages/root/logic.dart b/packages/chicken/lib/features/jahad/presentation/pages/root/logic.dart new file mode 100644 index 0000000..e31a5eb --- /dev/null +++ b/packages/chicken/lib/features/jahad/presentation/pages/root/logic.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/jahad/data/repositories/jahad_repository.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/routes/pages.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/pages/home/view.dart'; +import 'package:rasadyar_chicken/features/common/presentation/page/profile/view.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/utils/utils.dart'; + +import 'package:rasadyar_core/core.dart'; + +enum ErrorLocationType { serviceDisabled, permissionDenied, none } + +class JahadRootLogic extends GetxController { + var tokenService = Get.find(); + + late JahadRepository jahadRepository; + + RxList errorLocationType = RxList(); + RxMap homeExpandedList = RxMap(); + DateTime? _lastBackPressed; + + RxInt currentPage = 0.obs; + + final pages = [ + Navigator( + key: Get.nestedKey(jahadActionKey), + onGenerateRoute: (settings) { + final page = JahadPages.pages.firstWhere( + (e) => e.name == settings.name, + orElse: () => JahadPages.pages.firstWhere( + (e) => e.name == JahadRoutes.homeJahad, + ), + ); + return buildRouteFromGetPage(page); + }, + ), + + ProfilePage(), + ]; + + @override + void onInit() { + super.onInit(); + jahadRepository = diChicken.get(); + } + + void toggleExpanded(int index) { + if (homeExpandedList.keys.contains(index)) { + homeExpandedList.remove(index); + } else { + homeExpandedList[index] = false; + } + } + + void rootErrorHandler(DioException error) { + handleGeneric(error, () { + tokenService.deleteModuleTokens(Module.chicken); + }); + } + + void changePage(int index) { + currentPage.value = index; + } + + void popBackTaped() async { + 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(); + } + } +} diff --git a/packages/chicken/lib/features/jahad/presentation/pages/root/view.dart b/packages/chicken/lib/features/jahad/presentation/pages/root/view.dart new file mode 100644 index 0000000..88d350e --- /dev/null +++ b/packages/chicken/lib/features/jahad/presentation/pages/root/view.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class JahadRootPage extends GetView { + const JahadRootPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isFullScreen: true, + onPopScopTaped: controller.popBackTaped, + child: ObxValue((data) { + return Stack( + children: [ + IndexedStack(children: controller.pages, index: data.value), + Positioned( + right: 0, + left: 0, + bottom: 0, + child: RBottomNavigation( + mainAxisAlignment: MainAxisAlignment.spaceAround, + items: [ + RBottomNavigationItem( + label: 'خانه', + icon: Assets.vec.homeSvg.path, + isSelected: controller.currentPage.value == 0, + onTap: () { + Get.nestedKey( + jahadActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(0); + }, + ), + RBottomNavigationItem( + label: 'پروفایل', + icon: Assets.vec.profileCircleSvg.path, + isSelected: controller.currentPage.value == 1, + onTap: () { + Get.nestedKey( + jahadActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(1); + }, + ), + ], + ), + ), + ], + ); + }, controller.currentPage), + ); + } +} diff --git a/packages/chicken/lib/features/jahad/presentation/routes/pages.dart b/packages/chicken/lib/features/jahad/presentation/routes/pages.dart new file mode 100644 index 0000000..84584f3 --- /dev/null +++ b/packages/chicken/lib/features/jahad/presentation/routes/pages.dart @@ -0,0 +1,64 @@ +import 'package:rasadyar_chicken/features/jahad/presentation/pages/home/logic.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/pages/home/view.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/pages/root/logic.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/pages/root/view.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/pages/active_hatching/view.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/pages/new_inspection/logic.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/pages/new_inspection/view.dart'; +import 'package:rasadyar_chicken/features/jahad/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/routes/global_binding.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class JahadPages { + JahadPages._(); + + static List get pages => [ + GetPage( + name: JahadRoutes.initJahad, + page: () => JahadRootPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ChickenBaseLogic(), fenix: true); + Get.lazyPut(() => JahadRootLogic()); + Get.lazyPut(() => JahadHomeLogic()); + }), + ], + ), + GetPage( + name: JahadRoutes.homeJahad, + page: () => JahadHomePage(), + middlewares: [AuthMiddleware()], + binding: BindingsBuilder(() { + Get.put(JahadHomeLogic()); + Get.lazyPut(() => ChickenBaseLogic()); + }), + ), + + GetPage( + name: JahadRoutes.activeHatchingJahad, + page: () => ActiveHatchingPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ActiveHatchingLogic()); + }), + ], + ), + GetPage( + name: JahadRoutes.newInspectionJahad, + page: () => NewInspectionPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => NewInspectionLogic()); + }), + ], + ), + ]; +} diff --git a/packages/chicken/lib/features/jahad/presentation/routes/routes.dart b/packages/chicken/lib/features/jahad/presentation/routes/routes.dart new file mode 100644 index 0000000..d651478 --- /dev/null +++ b/packages/chicken/lib/features/jahad/presentation/routes/routes.dart @@ -0,0 +1,10 @@ +sealed class JahadRoutes { + JahadRoutes._(); + + static const _base = '/chicken/jahad'; + static const initJahad = '$_base/'; + static const homeJahad = '$_base/home'; + static const actionJahad = '$_base/action'; + static const activeHatchingJahad = '$_base/activeHatching'; + static const newInspectionJahad = '$_base/newInspection'; +} diff --git a/packages/chicken/lib/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart b/packages/chicken/lib/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart index 9cf0dac..50b32f2 100644 --- a/packages/chicken/lib/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart +++ b/packages/chicken/lib/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart @@ -1,4 +1,16 @@ +/// مدل داده‌ای برای ارسال اطلاعات بازرسی مرغداری به سرور +/// +/// این کلاس شامل تمام اطلاعات مربوط به بازرسی یک واحد مرغداری است که شامل: +/// - موقعیت جغرافیایی (عرض و طول جغرافیایی) +/// - شناسه جوجه‌ریزی +/// - نقش کاربر +/// - اطلاعات مختلف بازرسی در بخش‌های مختلف +/// +/// این مدل برای ارسال درخواست POST به endpoint `/poultry_science_report/` استفاده می‌شود. class SubmitInspectionResponse { + /// سازنده کلاس SubmitInspectionResponse + /// + /// تمام پارامترها اختیاری هستند و می‌توانند null باشند. SubmitInspectionResponse({ this.lat, this.log, @@ -15,20 +27,49 @@ class SubmitInspectionResponse { this.inspectionNotes, }); + /// عرض جغرافیایی (Latitude) محل بازرسی String? lat; + + /// طول جغرافیایی (Longitude) محل بازرسی String? log; + + /// شناسه یکتای جوجه‌ریزی که بازرسی برای آن انجام می‌شود int? poultryHatchingId; + + /// نقش کاربری که بازرسی را انجام می‌دهد String? role; + + /// اطلاعات مربوط به شرایط عمومی سالن مرغداری GeneralConditionHall? generalConditionHall; + + /// اطلاعات مربوط به تلفات و خسارات Casualties? casualties; + + /// اطلاعات مربوط به کارشناسان فنی TechnicalOfficer? technicalOfficer; + + /// اطلاعات مربوط به وضعیت نهاده‌ها InputStatus? inputStatus; + + /// اطلاعات مربوط به زیرساخت و انرژی InfrastructureEnergy? infrastructureEnergy; + + /// اطلاعات مربوط به نیروی انسانی Hr? hr; + + /// اطلاعات مربوط به تسهیلات Facilities? facilities; + + /// وضعیت بازرسی (مثلاً: انجام شده، در حال انجام، لغو شده) String? inspectionStatus; + + /// یادداشت‌ها و توضیحات بازرس String? inspectionNotes; + /// تبدیل شیء به فرمت JSON برای ارسال به سرور + /// + /// تمام اطلاعات بازرسی را در یک ساختار JSON سازماندهی می‌کند. + /// بخش‌های مختلف بازرسی در زیر کلید `report_information` قرار می‌گیرند. Map toJson() { return { 'lat': lat, @@ -49,6 +90,9 @@ class SubmitInspectionResponse { }; } + /// ساخت شیء از JSON دریافتی از سرور + /// + /// این متد برای تبدیل پاسخ JSON سرور به یک شیء SubmitInspectionResponse استفاده می‌شود. factory SubmitInspectionResponse.fromJson(Map json) { return SubmitInspectionResponse( lat: json['lat'].toString(), @@ -88,7 +132,11 @@ class SubmitInspectionResponse { } } +/// اطلاعات مربوط به شرایط عمومی سالن مرغداری +/// +/// این کلاس شامل اطلاعاتی در مورد وضعیت سلامت، تهویه، بستر، دما و آب آشامیدنی سالن است. class GeneralConditionHall { + /// سازنده کلاس GeneralConditionHall GeneralConditionHall({ this.images, this.healthStatus, @@ -99,12 +147,25 @@ class GeneralConditionHall { this.drinkingWaterQuality, }); + /// لیست آدرس تصاویر مربوط به شرایط سالن List? images; + + /// وضعیت سلامت سالن String? healthStatus; + + /// وضعیت سیستم تهویه String? ventilationStatus; + + /// وضعیت بستر سالن String? bedCondition; + + /// دمای سالن String? temperature; + + /// منبع آب آشامیدنی String? drinkingWaterSource; + + /// کیفیت آب آشامیدنی String? drinkingWaterQuality; Map toJson() { @@ -134,7 +195,12 @@ class GeneralConditionHall { } } +/// اطلاعات مربوط به تلفات و خسارات +/// +/// این کلاس شامل اطلاعاتی در مورد تلفات عادی و غیرعادی، منبع جوجه‌ریزی، +/// علت تلفات غیرعادی، نوع بیماری و نمونه‌برداری است. class Casualties { + /// سازنده کلاس Casualties Casualties({ this.normalLosses, this.abnormalLosses, @@ -146,13 +212,28 @@ class Casualties { this.images, }); + /// تعداد تلفات عادی int? normalLosses; + + /// تعداد تلفات غیرعادی int? abnormalLosses; + + /// منبع جوجه‌ریزی String? sourceOfHatching; + + /// علت تلفات غیرعادی String? causeAbnormalLosses; + + /// نوع بیماری (در صورت وجود) String? typeDisease; + + /// آیا نمونه‌برداری انجام شده است؟ bool? samplingDone; + + /// نوع نمونه‌برداری انجام شده String? typeSampling; + + /// لیست آدرس تصاویر مربوط به تلفات List? images; Map toJson() { @@ -184,13 +265,20 @@ class Casualties { } } +/// اطلاعات مربوط به کارشناسان فنی +/// +/// این کلاس شامل اطلاعات کارشناس بهداشت و کارشناس فنی مهندسی است. class TechnicalOfficer { + /// سازنده کلاس TechnicalOfficer TechnicalOfficer({ this.technicalHealthOfficer, this.technicalEngineeringOfficer, }); + /// نام یا اطلاعات کارشناس بهداشت String? technicalHealthOfficer; + + /// نام یا اطلاعات کارشناس فنی مهندسی String? technicalEngineeringOfficer; Map toJson() { @@ -209,7 +297,12 @@ class TechnicalOfficer { } } +/// اطلاعات مربوط به وضعیت نهاده‌ها +/// +/// این کلاس شامل اطلاعاتی در مورد نهاده‌های مصرفی مرغداری شامل: +/// وضعیت نهاده، نام شرکت، کد رهگیری، نوع دان، موجودی انبار و درجه دان است. class InputStatus { + /// سازنده کلاس InputStatus InputStatus({ this.inputStatus, this.companyName, @@ -221,13 +314,28 @@ class InputStatus { this.images, }); + /// وضعیت نهاده‌ها String? inputStatus; + + /// نام شرکت تامین‌کننده نهاده String? companyName; + + /// کد رهگیری نهاده String? trackingCode; + + /// نوع دان مصرفی String? typeOfGrain; + + /// موجودی نهاده در انبار String? inventoryInWarehouse; + + /// موجودی نهاده تا زمان بازدید String? inventoryUntilVisit; + + /// درجه دان String? gradeGrain; + + /// لیست آدرس تصاویر مربوط به نهاده‌ها List? images; Map toJson() { @@ -259,7 +367,12 @@ class InputStatus { } } +/// اطلاعات مربوط به زیرساخت و انرژی +/// +/// این کلاس شامل اطلاعاتی در مورد ژنراتور، سوخت، قطعی برق و سایر +/// اطلاعات مربوط به زیرساخت انرژی مرغداری است. class InfrastructureEnergy { + /// سازنده کلاس InfrastructureEnergy InfrastructureEnergy({ this.generatorType, this.generatorModel, @@ -274,16 +387,37 @@ class InfrastructureEnergy { this.additionalNotes, }); + /// نوع ژنراتور String? generatorType; + + /// مدل ژنراتور String? generatorModel; + + /// تعداد ژنراتورها String? generatorCount; + + /// ظرفیت ژنراتور String? generatorCapacity; + + /// نوع سوخت ژنراتور String? fuelType; + + /// عملکرد ژنراتور String? generatorPerformance; + + /// موجودی سوخت اضطراری String? emergencyFuelInventory; + + /// آیا سابقه قطعی برق وجود دارد؟ bool? hasPowerCutHistory; + + /// مدت زمان قطعی برق String? powerCutDuration; + + /// ساعت قطعی برق String? powerCutHour; + + /// یادداشت‌های اضافی String? additionalNotes; Map toJson() { @@ -319,7 +453,12 @@ class InfrastructureEnergy { } } +/// اطلاعات مربوط به نیروی انسانی +/// +/// این کلاس شامل اطلاعاتی در مورد تعداد کارکنان، وضعیت بومی/غیربومی بودن +/// کارکنان، وضعیت قرارداد و آموزش کارکنان است. class Hr { + /// سازنده کلاس Hr Hr({ this.numberEmployed, this.numberIndigenous, @@ -328,10 +467,19 @@ class Hr { this.trained, }); + /// تعداد کل کارکنان int? numberEmployed; + + /// تعداد کارکنان بومی int? numberIndigenous; + + /// تعداد کارکنان غیربومی int? numberNonIndigenous; + + /// وضعیت قرارداد کارکنان String? contractStatus; + + /// آیا کارکنان آموزش دیده‌اند؟ bool? trained; Map toJson() { @@ -355,7 +503,12 @@ class Hr { } } +/// اطلاعات مربوط به تسهیلات +/// +/// این کلاس شامل اطلاعاتی در مورد تسهیلات دریافتی مرغداری شامل: +/// وجود تسهیلات، نوع تسهیلات، مبلغ، تاریخ و وضعیت بازپرداخت است. class Facilities { + /// سازنده کلاس Facilities Facilities({ this.hasFacilities, this.typeOfFacility, @@ -365,11 +518,22 @@ class Facilities { this.requestFacilities, }); + /// آیا تسهیلات دریافت شده است؟ bool? hasFacilities; + + /// نوع تسهیلات String? typeOfFacility; + + /// مبلغ تسهیلات int? amount; + + /// تاریخ دریافت تسهیلات String? date; + + /// وضعیت بازپرداخت تسهیلات String? repaymentStatus; + + /// درخواست تسهیلات String? requestFacilities; Map toJson() { diff --git a/packages/chicken/lib/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response_schema.json b/packages/chicken/lib/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response_schema.json new file mode 100644 index 0000000..870f300 --- /dev/null +++ b/packages/chicken/lib/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response_schema.json @@ -0,0 +1,424 @@ +{ + "description": "ساختار و توضیحات کامل مدل SubmitInspectionResponse برای ارسال اطلاعات بازرسی مرغداری", + "endpoint": "/poultry_science_report/", + "method": "POST", + "schema": { + "lat": { + "type": "string", + "required": false, + "description": "عرض جغرافیایی (Latitude) محل بازرسی", + "example": "35.6892" + }, + "log": { + "type": "string", + "required": false, + "description": "طول جغرافیایی (Longitude) محل بازرسی", + "example": "51.3890" + }, + "hatching_id": { + "type": "integer", + "required": false, + "description": "شناسه یکتای جوجه‌ریزی که بازرسی برای آن انجام می‌شود", + "example": 12345 + }, + "role": { + "type": "string", + "required": false, + "description": "نقش کاربری که بازرسی را انجام می‌دهد", + "example": "inspector" + }, + "report_information": { + "type": "object", + "required": false, + "description": "اطلاعات کامل بازرسی در بخش‌های مختلف", + "properties": { + "general_condition_hall": { + "type": "object", + "required": false, + "description": "اطلاعات مربوط به شرایط عمومی سالن مرغداری", + "properties": { + "images": { + "type": "array", + "items": { + "type": "string" + }, + "required": false, + "description": "لیست آدرس تصاویر مربوط به شرایط سالن" + }, + "health_status": { + "type": "string", + "required": false, + "description": "وضعیت سلامت سالن" + }, + "ventilation_status": { + "type": "string", + "required": false, + "description": "وضعیت سیستم تهویه" + }, + "bed_condition": { + "type": "string", + "required": false, + "description": "وضعیت بستر سالن" + }, + "temperature": { + "type": "string", + "required": false, + "description": "دمای سالن" + }, + "drinking_water_source": { + "type": "string", + "required": false, + "description": "منبع آب آشامیدنی" + }, + "drinking_water_quality": { + "type": "string", + "required": false, + "description": "کیفیت آب آشامیدنی" + } + } + }, + "casualties": { + "type": "object", + "required": false, + "description": "اطلاعات مربوط به تلفات و خسارات", + "properties": { + "normal_losses": { + "type": "integer", + "required": false, + "description": "تعداد تلفات عادی", + "example": 50 + }, + "abnormal_losses": { + "type": "integer", + "required": false, + "description": "تعداد تلفات غیرعادی", + "example": 10 + }, + "source_of_hatching": { + "type": "string", + "required": false, + "description": "منبع جوجه‌ریزی" + }, + "cause_abnormal_losses": { + "type": "string", + "required": false, + "description": "علت تلفات غیرعادی" + }, + "type_disease": { + "type": "string", + "required": false, + "description": "نوع بیماری (در صورت وجود)" + }, + "sampling_done": { + "type": "boolean", + "required": false, + "description": "آیا نمونه‌برداری انجام شده است؟", + "example": true + }, + "type_sampling": { + "type": "string", + "required": false, + "description": "نوع نمونه‌برداری انجام شده" + }, + "images": { + "type": "array", + "items": { + "type": "string" + }, + "required": false, + "description": "لیست آدرس تصاویر مربوط به تلفات" + } + } + }, + "technical_officer": { + "type": "object", + "required": false, + "description": "اطلاعات مربوط به کارشناسان فنی", + "properties": { + "technical_health_officer": { + "type": "string", + "required": false, + "description": "نام یا اطلاعات کارشناس بهداشت" + }, + "technical_engineering_officer": { + "type": "string", + "required": false, + "description": "نام یا اطلاعات کارشناس فنی مهندسی" + } + } + }, + "input_status": { + "type": "object", + "required": false, + "description": "اطلاعات مربوط به وضعیت نهاده‌ها", + "properties": { + "input_status": { + "type": "string", + "required": false, + "description": "وضعیت نهاده‌ها" + }, + "company_name": { + "type": "string", + "required": false, + "description": "نام شرکت تامین‌کننده نهاده" + }, + "tracking_code": { + "type": "string", + "required": false, + "description": "کد رهگیری نهاده" + }, + "type_of_grain": { + "type": "string", + "required": false, + "description": "نوع دان مصرفی" + }, + "inventory_in_warehouse": { + "type": "string", + "required": false, + "description": "موجودی نهاده در انبار" + }, + "inventory_until_visit": { + "type": "string", + "required": false, + "description": "موجودی نهاده تا زمان بازدید" + }, + "grade_grain": { + "type": "string", + "required": false, + "description": "درجه دان" + }, + "images": { + "type": "array", + "items": { + "type": "string" + }, + "required": false, + "description": "لیست آدرس تصاویر مربوط به نهاده‌ها" + } + } + }, + "infrastructure_energy": { + "type": "object", + "required": false, + "description": "اطلاعات مربوط به زیرساخت و انرژی", + "properties": { + "generator_type": { + "type": "string", + "required": false, + "description": "نوع ژنراتور" + }, + "generator_model": { + "type": "string", + "required": false, + "description": "مدل ژنراتور" + }, + "generator_count": { + "type": "string", + "required": false, + "description": "تعداد ژنراتورها" + }, + "generator_capacity": { + "type": "string", + "required": false, + "description": "ظرفیت ژنراتور" + }, + "fuel_type": { + "type": "string", + "required": false, + "description": "نوع سوخت ژنراتور" + }, + "generator_performance": { + "type": "string", + "required": false, + "description": "عملکرد ژنراتور" + }, + "emergency_fuel_inventory": { + "type": "string", + "required": false, + "description": "موجودی سوخت اضطراری" + }, + "has_power_cut_history": { + "type": "boolean", + "required": false, + "description": "آیا سابقه قطعی برق وجود دارد؟", + "example": true + }, + "power_cut_duration": { + "type": "string", + "required": false, + "description": "مدت زمان قطعی برق" + }, + "power_cut_hour": { + "type": "string", + "required": false, + "description": "ساعت قطعی برق" + }, + "additional_notes": { + "type": "string", + "required": false, + "description": "یادداشت‌های اضافی" + } + } + }, + "hr": { + "type": "object", + "required": false, + "description": "اطلاعات مربوط به نیروی انسانی", + "properties": { + "number_employed": { + "type": "integer", + "required": false, + "description": "تعداد کل کارکنان", + "example": 15 + }, + "number_indigenous": { + "type": "integer", + "required": false, + "description": "تعداد کارکنان بومی", + "example": 10 + }, + "number_non_indigenous": { + "type": "integer", + "required": false, + "description": "تعداد کارکنان غیربومی", + "example": 5 + }, + "contract_status": { + "type": "string", + "required": false, + "description": "وضعیت قرارداد کارکنان" + }, + "trained": { + "type": "boolean", + "required": false, + "description": "آیا کارکنان آموزش دیده‌اند؟", + "example": true + } + } + }, + "facilities": { + "type": "object", + "required": false, + "description": "اطلاعات مربوط به تسهیلات", + "properties": { + "has_facilities": { + "type": "boolean", + "required": false, + "description": "آیا تسهیلات دریافت شده است؟", + "example": true + }, + "type_of_facility": { + "type": "string", + "required": false, + "description": "نوع تسهیلات" + }, + "amount": { + "type": "integer", + "required": false, + "description": "مبلغ تسهیلات", + "example": 500000000 + }, + "date": { + "type": "string", + "required": false, + "description": "تاریخ دریافت تسهیلات" + }, + "repayment_status": { + "type": "string", + "required": false, + "description": "وضعیت بازپرداخت تسهیلات" + }, + "request_facilities": { + "type": "string", + "required": false, + "description": "درخواست تسهیلات" + } + } + }, + "inspection_status": { + "type": "string", + "required": false, + "description": "وضعیت بازرسی (مثلاً: انجام شده، در حال انجام، لغو شده)" + }, + "inspection_notes": { + "type": "string", + "required": false, + "description": "یادداشت‌ها و توضیحات بازرس" + } + } + } + }, + "example_request": { + "lat": "35.6892", + "log": "51.3890", + "hatching_id": 12345, + "role": "inspector", + "report_information": { + "general_condition_hall": { + "images": [ + "https://example.com/image1.jpg" + ], + "health_status": "خوب", + "ventilation_status": "مناسب", + "bed_condition": "تمیز", + "temperature": "25", + "drinking_water_source": "چاه", + "drinking_water_quality": "عالی" + }, + "casualties": { + "normal_losses": 50, + "abnormal_losses": 10, + "source_of_hatching": "مرکز جوجه‌کشی", + "cause_abnormal_losses": "بیماری", + "type_disease": "نیوکاسل", + "sampling_done": true, + "type_sampling": "خون", + "images": [] + }, + "technical_officer": { + "technical_health_officer": "احمد محمدی", + "technical_engineering_officer": "علی رضایی" + }, + "input_status": { + "input_status": "کافی", + "company_name": "شرکت دان", + "tracking_code": "TR123456", + "type_of_grain": "استارتر", + "inventory_in_warehouse": "5000 کیلوگرم", + "inventory_until_visit": "4500 کیلوگرم", + "grade_grain": "A", + "images": [] + }, + "infrastructure_energy": { + "generator_type": "دیزلی", + "generator_model": "کامینز", + "generator_count": "2", + "generator_capacity": "100 کیلووات", + "fuel_type": "گازوئیل", + "generator_performance": "عالی", + "emergency_fuel_inventory": "5000 لیتر", + "has_power_cut_history": true, + "power_cut_duration": "2 ساعت", + "power_cut_hour": "14:00", + "additional_notes": "ژنراتورها به درستی نگهداری می‌شوند" + }, + "hr": { + "number_employed": 15, + "number_indigenous": 10, + "number_non_indigenous": 5, + "contract_status": "قراردادی", + "trained": true + }, + "facilities": { + "has_facilities": true, + "type_of_facility": "وام", + "amount": 500000000, + "date": "1403/01/15", + "repayment_status": "در حال بازپرداخت", + "request_facilities": "درخواست تسهیلات جدید" + }, + "inspection_status": "انجام شده", + "inspection_notes": "بازرسی با موفقیت انجام شد" + } + } +} \ No newline at end of file diff --git a/packages/chicken/lib/features/province_inspector/data/datasources/remote/province_inspector_remote_data_source.dart b/packages/chicken/lib/features/province_inspector/data/datasources/remote/province_inspector_remote_data_source.dart new file mode 100644 index 0000000..6577584 --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/data/datasources/remote/province_inspector_remote_data_source.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class ProvinceInspectorRemoteDataSource { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/province_inspector/data/datasources/remote/province_inspector_remote_data_source_impl.dart b/packages/chicken/lib/features/province_inspector/data/datasources/remote/province_inspector_remote_data_source_impl.dart new file mode 100644 index 0000000..e07a69e --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/data/datasources/remote/province_inspector_remote_data_source_impl.dart @@ -0,0 +1,40 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/province_inspector/data/datasources/remote/province_inspector_remote_data_source.dart'; +import 'package:rasadyar_core/core.dart'; + +class ProvinceInspectorRemoteDataSourceImpl + implements ProvinceInspectorRemoteDataSource { + final DioRemote _httpClient; + + ProvinceInspectorRemoteDataSourceImpl(this._httpClient); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + var res = await _httpClient.get( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + queryParameters: queryParameters, + fromJson: (json) => PaginationModel.fromJson( + json, + (json) => PoultryScienceReport.fromJson(json as Map), + ), + ); + return res.data; + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + await _httpClient.post( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + data: request.toJson(), + ); + } +} diff --git a/packages/chicken/lib/features/province_inspector/data/di/province_inspector_di.dart b/packages/chicken/lib/features/province_inspector/data/di/province_inspector_di.dart new file mode 100644 index 0000000..e7e332d --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/data/di/province_inspector_di.dart @@ -0,0 +1,36 @@ +import 'package:rasadyar_chicken/features/province_inspector/data/datasources/remote/province_inspector_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/province_inspector/data/datasources/remote/province_inspector_remote_data_source_impl.dart'; +import 'package:rasadyar_chicken/features/province_inspector/data/repositories/province_inspector_repository.dart'; +import 'package:rasadyar_chicken/features/province_inspector/data/repositories/province_inspector_repository_impl.dart'; +import 'package:rasadyar_core/core.dart'; + +/// Setup dependency injection for province_inspector feature +Future setupProvinceInspectorDI(GetIt di, DioRemote dioRemote) async { + di.registerLazySingleton( + () => ProvinceInspectorRemoteDataSourceImpl(dioRemote), + ); + + di.registerLazySingleton( + () => ProvinceInspectorRepositoryImpl(di.get()), + ); +} + +/// Re-register province_inspector dependencies (used when base URL changes) +Future reRegisterProvinceInspectorDI(GetIt di, DioRemote dioRemote) async { + await reRegister(di, () => ProvinceInspectorRemoteDataSourceImpl(dioRemote)); + await reRegister( + di, + () => ProvinceInspectorRepositoryImpl(di.get()), + ); +} + +/// Helper function to re-register a dependency +Future reRegister( + GetIt di, + T Function() factory, +) async { + if (di.isRegistered()) { + await di.unregister(); + } + di.registerLazySingleton(factory); +} diff --git a/packages/chicken/lib/features/province_inspector/data/repositories/province_inspector_repository.dart b/packages/chicken/lib/features/province_inspector/data/repositories/province_inspector_repository.dart new file mode 100644 index 0000000..2c68b3d --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/data/repositories/province_inspector_repository.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class ProvinceInspectorRepository { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/province_inspector/data/repositories/province_inspector_repository_impl.dart b/packages/chicken/lib/features/province_inspector/data/repositories/province_inspector_repository_impl.dart new file mode 100644 index 0000000..98b23e4 --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/data/repositories/province_inspector_repository_impl.dart @@ -0,0 +1,30 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/province_inspector/data/datasources/remote/province_inspector_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/province_inspector/data/repositories/province_inspector_repository.dart'; +import 'package:rasadyar_core/core.dart'; + +class ProvinceInspectorRepositoryImpl implements ProvinceInspectorRepository { + final ProvinceInspectorRemoteDataSource _remote; + + ProvinceInspectorRepositoryImpl(this._remote); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + return await _remote.getSubmitInspectionList( + token: token, + queryParameters: queryParameters, + ); + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + return await _remote.submitInspection(token: token, request: request); + } +} diff --git a/packages/chicken/lib/features/province_inspector/presentation/pages/active_hatching/logic.dart b/packages/chicken/lib/features/province_inspector/presentation/pages/active_hatching/logic.dart new file mode 100644 index 0000000..0a014aa --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/presentation/pages/active_hatching/logic.dart @@ -0,0 +1,95 @@ +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/repositories/poultry_science_repository.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/pages/root/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingLogic extends GetxController { + ProvinceInspectorRootLogic rootLogic = Get.find(); + BaseLogic baseLogic = Get.find(); + late PoultryScienceRepository poultryScienceRepository; + Rx>> activeHatchingList = + Resource>.loading().obs; + + final RxBool isLoadingMoreList = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + List routesName = ['اقدام', 'جوجه ریزی فعال']; + + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + RxnString searchedValue = RxnString(); + + @override + void onInit() { + super.onInit(); + poultryScienceRepository = diChicken.get(); + } + + @override + void onReady() { + super.onReady(); + getHatchingList(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getHatchingList([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreList.value = true; + } else { + activeHatchingList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => await poultryScienceRepository.getHatchingPoultry( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + queryParams: {'type': 'hatching'}, + role: 'ProvinceInspector', + pageSize: 50, + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + activeHatchingList.value = + Resource>.empty(); + } else { + activeHatchingList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(activeHatchingList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + Future onRefresh() async { + currentPage.value = 1; + await getHatchingList(); + } +} diff --git a/packages/chicken/lib/features/province_inspector/presentation/pages/active_hatching/view.dart b/packages/chicken/lib/features/province_inspector/presentation/pages/active_hatching/view.dart new file mode 100644 index 0000000..4fa0611 --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/presentation/pages/active_hatching/view.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet_logic.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingPage extends GetView { + const ActiveHatchingPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasSearch: true, + hasFilter: false, + backId: provinceInspectorActionKey, + routes: controller.routesName, + onSearchChanged: (data) { + controller.searchedValue.value = data; + controller.getHatchingList(); + }, + child: hatchingWidget(), + /*widgets: [ + hatchingWidget() + ],*/ + ); + } + + Widget hatchingWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidget(item), + secondChild: itemListExpandedWidget(item), + labelColor: AppColor.blueLight, + labelIcon: Assets.vec.activeFramSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getHatchingList(true), + ); + }, controller.activeHatchingList); + } + + Container itemListExpandedWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.center, + style: AppFonts.yekan16.copyWith(color: AppColor.greenDark), + ), + Spacer(), + + Visibility( + child: Text( + item.violation == true ? 'پیگیری' : 'عادی', + textAlign: TextAlign.center, + style: AppFonts.yekan10.copyWith(color: AppColor.redDark), + ), + ), + ], + ), + 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: [ + Text( + 'نژاد:${item.breed?.first.breed ?? 'N/A'}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + Text( + ' سن${item.age} (روز)', + + style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal), + ), + Text( + ' دوره جوجه ریزی:${item.period}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ], + ), + ), + + buildRow( + title: 'شماره مجوز جوجه ریزی', + value: item.licenceNumber ?? 'N/A', + ), + buildUnitRow( + title: 'حجم جوجه ریزی', + value: item.quantity.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'مانده در سالن', + value: item.leftOver.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'تلفات', + value: item.losses.separatedByCommaFa, + unit: '(قطعه)', + ), + buildRow( + title: 'دامپزشک فارم', + value: + '${item.vetFarm?.vetFarmFullName}(${item.vetFarm?.vetFarmMobile})', + ), + buildRow( + title: 'شرح بازرسی', + value: item.reportInfo?.image == false + ? 'ارسال تصویر جوجه ریزی فارم ' + : 'تکمیل شده', + titleStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + valueStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + ), + + RElevated( + height: 40.h, + isFullWidth: true, + onPressed: () { + Get.find().setHatchingModel( + item, + ); + Get.bottomSheet( + CreateInspectionBottomSheet(), + isScrollControlled: true, + ignoreSafeArea: false, + ).then((value) { + if (Get.isRegistered()) { + Get.find().clearForm(); + } + }); + }, + child: Text('ثبت بازرسی'), + ), + ], + ), + ); + } + + Widget itemListWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + Text( + item.poultry?.user?.mobile ?? '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: [ + Text( + item.poultry?.unitName ?? 'N/Aaq', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.bgDark), + ), + Text( + item.poultry?.licenceNumber ?? 'N/A', + textAlign: TextAlign.left, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + ], + ), + ), + Expanded( + flex: 1, + child: Assets.vec.scanSvg.svg( + width: 32.w, + height: 32.h, + colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn), + ), + ), + ], + ); + } +} diff --git a/packages/chicken/lib/features/province_inspector/presentation/pages/home/logic.dart b/packages/chicken/lib/features/province_inspector/presentation/pages/home/logic.dart new file mode 100644 index 0000000..5400e75 --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/presentation/pages/home/logic.dart @@ -0,0 +1,30 @@ +import 'package:rasadyar_chicken/features/province_inspector/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/routes/routes.dart'; +import 'package:rasadyar_core/core.dart'; + +class ProvinceInspectorActionItem { + final String title; + final String route; + final String icon; + + ProvinceInspectorActionItem({ + required this.title, + required this.route, + required this.icon, + }); +} + +class ProvinceInspectorHomeLogic extends GetxController { + RxList items = [ + ProvinceInspectorActionItem( + title: "جوجه ریزی فعال", + route: ProvinceInspectorRoutes.activeHatchingProvinceInspector, + icon: Assets.vec.activeFramSvg.path, + ), + ProvinceInspectorActionItem( + title: "بازرسی مزارع طیور", + route: ProvinceInspectorRoutes.newInspectionProvinceInspector, + icon: Assets.vec.activeFramSvg.path, + ), + ].obs; +} diff --git a/packages/chicken/lib/features/province_inspector/presentation/pages/home/view.dart b/packages/chicken/lib/features/province_inspector/presentation/pages/home/view.dart new file mode 100644 index 0000000..39eaaad --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/presentation/pages/home/view.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class ProvinceInspectorHomePage extends GetView { + ProvinceInspectorHomePage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isBase: true, + hasNews: true, + hasNotification: true, + child: gridWidget(), + ); + } + + Widget gridWidget() { + return ObxValue((data) { + return GridView.builder( + physics: BouncingScrollPhysics(), + padding: EdgeInsets.symmetric(vertical: 18.h, horizontal: 32.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 24.h, + crossAxisSpacing: 24.w, + ), + itemCount: data.length, + hitTestBehavior: HitTestBehavior.opaque, + itemBuilder: (BuildContext context, int index) { + var item = data[index]; + return GlassMorphismCardIcon( + title: item.title, + vecIcon: item.icon, + onTap: () async { + Get.toNamed(item.route, id: provinceInspectorActionKey); + }, + ); + }, + ); + }, controller.items); + } +} diff --git a/packages/chicken/lib/features/province_inspector/presentation/pages/new_inspection/logic.dart b/packages/chicken/lib/features/province_inspector/presentation/pages/new_inspection/logic.dart new file mode 100644 index 0000000..92f10c5 --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/presentation/pages/new_inspection/logic.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/pages/root/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class NewInspectionLogic extends GetxController { + BaseLogic baseLogic = Get.find(); + + Rx>> submitInspectionList = + Resource>.loading().obs; + + ProvinceInspectorRootLogic rootLogic = Get.find(); + + final RxBool isLoadingMoreAllocationsMade = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + RxList pickedImages = [].obs; + final List _multiPartPickedImages = []; + + RxBool isOnUpload = false.obs; + + RxDouble presentUpload = 0.0.obs; + RxList routesName = RxList(); + RxInt selectedSegmentIndex = 0.obs; + + RxnString searchedValue = RxnString(); + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + + @override + void onInit() { + super.onInit(); + + routesName.value = ['اقدام'].toList(); + + ever(selectedSegmentIndex, (callback) { + routesName.removeLast(); + routesName.add(callback == 0 ? 'بازرسی' : 'بایگانی'); + }); + } + + @override + void onReady() { + super.onReady(); + + getReport(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getReport([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreAllocationsMade.value = true; + } else { + submitInspectionList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => + await rootLogic.provinceInspectorRepository.getSubmitInspectionList( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + role: 'ProvinceInspector', + pageSize: 50, + search: 'filter', + + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + submitInspectionList.value = + Resource>.empty(); + } else { + submitInspectionList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(submitInspectionList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + Future pickImages() async { + determineCurrentPosition(); + var tmp = await pickCameraImage(); + if (tmp?.path != null && pickedImages.length < 7) { + pickedImages.add(tmp!); + } + } + + void removeImage(int index) { + pickedImages.removeAt(index); + } + + void closeBottomSheet() { + Get.back(); + } + + double calculateUploadProgress({required int sent, required int total}) { + if (total != 0) { + double progress = (sent * 100 / total) / 100; + return progress; + } else { + return 0.0; + } + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + void setSearchValue(String? data) { + dLog('Search Value: $data'); + searchedValue.value = data?.trim(); + getReport(); + } + + Future onRefresh() async { + currentPage.value = 1; + await getReport(); + } + + String getStatus(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return 'در حال بررسی'; + } + return status; + } + + Color getStatusColor(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return AppColor.yellowNormal; + } + // می‌توانید منطق رنگ را بر اساس inspectionStatus تنظیم کنید + return AppColor.greenNormal; + } +} diff --git a/packages/chicken/lib/features/province_inspector/presentation/pages/new_inspection/view.dart b/packages/chicken/lib/features/province_inspector/presentation/pages/new_inspection/view.dart new file mode 100644 index 0000000..38ee5ff --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/presentation/pages/new_inspection/view.dart @@ -0,0 +1,1134 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class NewInspectionPage extends GetView { + const NewInspectionPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasBack: true, + hasFilter: true, + hasSearch: true, + onFilterTap: () { + Get.bottomSheet(filterBottomSheet()); + }, + onRefresh: controller.onRefresh, + onSearchChanged: (data) => controller.setSearchValue(data), + backId: provinceInspectorActionKey, + routesWidget: ContainerBreadcrumb(rxRoutes: controller.routesName), + child: Column(children: [reportWidget()]), + ); + } + + Widget reportWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidgetReport(item), + secondChild: itemListExpandedWidgetReport(item), + labelColor: AppColor.greenLight, + labelIcon: Assets.vec.cubeSearchSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getReport(true), + ); + }, controller.submitInspectionList), + ); + } + + Widget itemListExpandedWidgetReport(PoultryScienceReport item) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + spacing: 8, + children: [ + buildRow( + title: 'وضعیت بازرسی', + value: controller.getStatus(item), + titleStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + valueStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + ), + if (item.hatching?.poultry?.unitName != null) + buildRow( + title: 'مرغداری', + value: item.hatching?.poultry?.unitName ?? '-', + ), + if (item.hatching?.id != null) + buildRow( + title: 'شناسه جوجه‌ریزی', + value: item.hatching!.id.toString(), + ), + + if (item + .reportInformation + ?.technicalOfficer + ?.technicalHealthOfficer != + null) + buildRow( + title: 'کارشناس بهداشت', + value: + item + .reportInformation! + .technicalOfficer! + .technicalHealthOfficer ?? + '-', + ), + if (item + .reportInformation + ?.technicalOfficer + ?.technicalEngineeringOfficer != + null) + buildRow( + title: 'کارشناس فنی', + value: + item + .reportInformation! + .technicalOfficer! + .technicalEngineeringOfficer ?? + '-', + ), + if (item.reportInformation?.casualties?.normalLosses != null || + item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات عادی', + value: (item.reportInformation?.casualties?.normalLosses ?? 0) + .toString(), + unit: '(قطعه)', + ), + if (item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات غیرعادی', + value: item.reportInformation!.casualties!.abnormalLosses + .toString(), + unit: '(قطعه)', + ), + + if (item.reportInformation?.inspectionNotes != null && + item.reportInformation!.inspectionNotes!.isNotEmpty) + buildRow( + title: 'یادداشت بازرسی', + value: item.reportInformation!.inspectionNotes ?? '-', + ), + + RElevated( + text: 'جزییات', + isFullWidth: true, + width: 150.w, + height: 40.h, + onPressed: () { + showDetailsBottomSheet(item); + }, + textStyle: AppFonts.yekan16.copyWith(color: Colors.white), + backgroundColor: AppColor.greenNormal, + ), + ], + ), + ); + } + + Row itemListWidgetReport(PoultryScienceReport item) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox(width: 20), + Expanded( + flex: 2, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 5, + children: [ + Text( + 'شناسه جوجه‌ریزی: ${item.hatching?.id ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + + Text( + item.createDate?.toJalali.formatCompactDate() ?? '-', + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + Expanded( + flex: 3, + child: Column( + spacing: 5, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'مرغداری: ${item.hatching?.poultry?.unitName ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + if (item.reportInformation?.inspectionStatus != null) + Text( + 'وضعیت بازرسی: ${item.reportInformation?.inspectionStatus ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + ], + ); + } + + void showDetailsBottomSheet(PoultryScienceReport item) { + Get.bottomSheet( + isScrollControlled: true, + BaseBottomSheet( + height: Get.height * 0.8, + rootChild: DetailsBottomSheetWidget(item: item), + ), + ); + } + + 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.getReport(); + Get.back(); + }, + height: 40, + ), + ], + ), + ); + } +} + +class DetailsBottomSheetWidget extends StatefulWidget { + final PoultryScienceReport item; + + const DetailsBottomSheetWidget({super.key, required this.item}); + + @override + State createState() => + _DetailsBottomSheetWidgetState(); +} + +class _DetailsBottomSheetWidgetState extends State { + int selectedTabIndex = 0; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 10, + ), + child: Text( + 'جزییات', + style: AppFonts.yekan18Bold.copyWith( + color: AppColor.iconColor, + ), + ), + ), + ], + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + tabBarWidget( + [ + 'اطلاعات کلی', + 'شرایط سالن', + 'تلفات', + 'کارشناس', + 'نهاده', + 'زیرساخت', + 'نیروی انسانی', + 'تسهیلات', + ], + selectedTabIndex, + (index) => setState(() => selectedTabIndex = index), + ), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _buildTableContent(), + ), + ), + ), + ], + ); + } + + Widget _buildTableContent() { + switch (selectedTabIndex) { + case 0: + return generalInfoTable(); + case 1: + return generalConditionHallTable(); + case 2: + return casualtiesTable(); + case 3: + return technicalOfficerTable(); + case 4: + return inputStatusTable(); + case 5: + return infrastructureEnergyTable(); + case 6: + return hrTable(); + case 7: + return facilitiesTable(); + default: + return generalInfoTable(); + } + } + + Widget technicalOfficerTable() { + final officer = widget.item.reportInformation?.technicalOfficer; + if (officer == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'کارشناس فنی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'کارشناس بهداشت', + value: officer.technicalHealthOfficer ?? '-', + ), + rTableRow( + title: 'کارشناس فنی', + value: officer.technicalEngineeringOfficer ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget generalInfoTable() { + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'اطلاعات کلی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت بازرسی', + value: + widget.item.reportInformation?.inspectionStatus ?? + widget.item.state ?? + '-', + ), + rTableRow( + title: 'یادداشت بازرسی', + value: widget.item.reportInformation?.inspectionNotes ?? '-', + ), + rTableRow(title: 'نقش', value: widget.item.reporterRole ?? '-'), + if (widget.item.lat != null && widget.item.log != null) + rTableRow( + title: 'موقعیت', + value: '${widget.item.lat}, ${widget.item.log}', + ), + if (widget.item.hatching?.id != null) + rTableRow( + title: 'شناسه جوجه ریزی', + value: widget.item.hatching!.id.toString(), + ), + ], + ), + ), + ], + ); + } + + Widget generalConditionHallTable() { + final hall = widget.item.reportInformation?.generalConditionHall; + if (hall == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'شرایط عمومی سالن', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow(title: 'وضعیت سلامت', value: hall.healthStatus ?? '-'), + rTableRow( + title: 'وضعیت تهویه', + value: hall.ventilationStatus ?? '-', + ), + rTableRow(title: 'وضعیت بستر', value: hall.bedCondition ?? '-'), + rTableRow(title: 'دما', value: hall.temperature ?? '-'), + rTableRow( + title: 'منبع آب آشامیدنی', + value: hall.drinkingWaterSource ?? '-', + ), + rTableRow( + title: 'کیفیت آب آشامیدنی', + value: hall.drinkingWaterQuality ?? '-', + ), + ], + ), + ), + if (hall.images != null && hall.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: hall.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(hall.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(hall.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget casualtiesTable() { + final casualties = widget.item.reportInformation?.casualties; + if (casualties == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تلفات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (casualties.normalLosses != null) + rTableRow( + title: 'تلفات عادی', + value: casualties.normalLosses.toString(), + ), + if (casualties.abnormalLosses != null) + rTableRow( + title: 'تلفات غیرعادی', + value: casualties.abnormalLosses.toString(), + ), + rTableRow( + title: 'منبع جوجه ریزی', + value: casualties.sourceOfHatching ?? '-', + ), + rTableRow( + title: 'علت تلفات غیرعادی', + value: casualties.causeAbnormalLosses ?? '-', + ), + rTableRow( + title: 'نوع بیماری', + value: casualties.typeDisease ?? '-', + ), + rTableRow( + title: 'نمونه‌برداری انجام شده', + value: casualties.samplingDone == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع نمونه‌برداری', + value: casualties.typeSampling ?? '-', + ), + ], + ), + ), + if (casualties.images != null && casualties.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: casualties.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(casualties.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(casualties.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget inputStatusTable() { + final inputStatus = widget.item.reportInformation?.inputStatus; + if (inputStatus == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'وضعیت نهاده', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت نهاده', + value: inputStatus.inputStatus ?? '-', + ), + rTableRow( + title: 'نام شرکت', + value: inputStatus.companyName ?? '-', + ), + rTableRow( + title: 'کد پیگیری', + value: inputStatus.trackingCode ?? '-', + ), + rTableRow( + title: 'نوع دانه', + value: inputStatus.typeOfGrain ?? '-', + ), + rTableRow( + title: 'موجودی در انبار', + value: inputStatus.inventoryInWarehouse ?? '-', + ), + rTableRow( + title: 'موجودی تا بازدید', + value: inputStatus.inventoryUntilVisit ?? '-', + ), + rTableRow( + title: 'درجه دانه', + value: inputStatus.gradeGrain ?? '-', + ), + ], + ), + ), + if (inputStatus.images != null && inputStatus.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: inputStatus.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(inputStatus.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(inputStatus.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget infrastructureEnergyTable() { + final infra = widget.item.reportInformation?.infrastructureEnergy; + if (infra == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'زیرساخت و انرژی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'نوع ژنراتور', + value: infra.generatorType ?? '-', + ), + rTableRow( + title: 'مدل ژنراتور', + value: infra.generatorModel ?? '-', + ), + rTableRow( + title: 'تعداد ژنراتور', + value: infra.generatorCount ?? '-', + ), + rTableRow( + title: 'ظرفیت ژنراتور', + value: infra.generatorCapacity ?? '-', + ), + rTableRow(title: 'نوع سوخت', value: infra.fuelType ?? '-'), + rTableRow( + title: 'عملکرد ژنراتور', + value: infra.generatorPerformance ?? '-', + ), + rTableRow( + title: 'موجودی سوخت اضطراری', + value: infra.emergencyFuelInventory ?? '-', + ), + rTableRow( + title: 'تاریخچه قطع برق', + value: infra.hasPowerCutHistory == true ? 'بله' : 'خیر', + ), + if (infra.hasPowerCutHistory == true) ...[ + rTableRow( + title: 'مدت قطع برق', + value: infra.powerCutDuration ?? '-', + ), + rTableRow( + title: 'ساعت قطع برق', + value: infra.powerCutHour ?? '-', + ), + ], + rTableRow( + title: 'یادداشت اضافی', + value: infra.additionalNotes ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget hrTable() { + final hr = widget.item.reportInformation?.hr; + if (hr == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'نیروی انسانی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (hr.numberEmployed != null) + rTableRow( + title: 'تعداد شاغلین', + value: hr.numberEmployed.toString(), + ), + if (hr.numberIndigenous != null) + rTableRow( + title: 'تعداد بومی', + value: hr.numberIndigenous.toString(), + ), + if (hr.numberNonIndigenous != null) + rTableRow( + title: 'تعداد غیربومی', + value: hr.numberNonIndigenous.toString(), + ), + rTableRow( + title: 'وضعیت قرارداد', + value: hr.contractStatus ?? '-', + ), + rTableRow( + title: 'آموزش دیده', + value: hr.trained == true ? 'بله' : 'خیر', + ), + ], + ), + ), + ], + ); + } + + Widget facilitiesTable() { + final facilities = widget.item.reportInformation?.facilities; + if (facilities == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تسهیلات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'دارای تسهیلات', + value: facilities.hasFacilities == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع تسهیلات', + value: facilities.typeOfFacility ?? '-', + ), + if (facilities.amount != null) + rTableRow(title: 'مبلغ', value: facilities.amount.toString()), + rTableRow(title: 'تاریخ', value: facilities.date ?? '-'), + rTableRow( + title: 'وضعیت بازپرداخت', + value: facilities.repaymentStatus ?? '-', + ), + rTableRow( + title: 'درخواست تسهیلات', + value: facilities.requestFacilities ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget tabBarWidget( + List tabs, + int selectedIndex, + Function(int) onTabSelected, + ) { + return SizedBox( + height: 40.h, + width: Get.width, + child: Column( + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + reverse: true, + child: Row( + children: [ + ...tabs.map( + (tab) => GestureDetector( + onTap: () => onTabSelected(tabs.indexOf(tab)), + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 11, + ), + decoration: BoxDecoration( + border: tab == tabs[selectedIndex] + ? Border( + bottom: BorderSide( + color: AppColor.blueNormalOld, + width: 3, + ), + ) + : null, + ), + child: Text( + tab, + style: AppFonts.yekan12Bold.copyWith( + color: tab == tabs[selectedIndex] + ? AppColor.blueNormalOld + : AppColor.mediumGrey, + ), + ), + ), + ), + ), + ], + ), + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + ], + ), + ); + } + + Row rTableRow({String? title, String? value}) { + return Row( + children: [ + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + color: AppColor.bgLight2, + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + title ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.iconColor), + ), + ), + ), + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + value ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + ), + ), + ], + ); + } + + void showImageDialog(List images, int initialIndex) { + final pageController = PageController(initialPage: initialIndex); + final currentIndex = initialIndex.obs; + + Get.dialog( + Dialog( + backgroundColor: Colors.transparent, + insetPadding: EdgeInsets.zero, + child: Container( + width: Get.width, + height: Get.height, + color: Colors.black, + child: Stack( + children: [ + PageView.builder( + controller: pageController, + itemCount: images.length, + onPageChanged: (index) { + currentIndex.value = index; + }, + itemBuilder: (context, index) { + return InteractiveViewer( + minScale: 0.5, + maxScale: 4.0, + child: Center( + child: Image.network( + images[index], + fit: BoxFit.contain, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + color: Colors.white, + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Center( + child: Icon( + Icons.error, + color: Colors.white, + size: 50, + ), + ); + }, + ), + ), + ); + }, + ), + Positioned( + top: 40, + right: 16, + child: IconButton( + icon: Icon(Icons.close, color: Colors.white, size: 30), + onPressed: () => Get.back(), + ), + ), + if (images.length > 1) + Positioned( + bottom: 20, + left: 0, + right: 0, + child: Center( + child: Obx( + () => Container( + padding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '${currentIndex.value + 1} / ${images.length}', + style: TextStyle(color: Colors.white, fontSize: 14), + ), + ), + ), + ), + ), + ], + ), + ), + ), + barrierDismissible: true, + ); + } +} diff --git a/packages/chicken/lib/features/province_inspector/presentation/pages/root/logic.dart b/packages/chicken/lib/features/province_inspector/presentation/pages/root/logic.dart new file mode 100644 index 0000000..48419f1 --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/presentation/pages/root/logic.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/province_inspector/data/repositories/province_inspector_repository.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/pages/home/view.dart'; +import 'package:rasadyar_chicken/features/common/presentation/page/profile/view.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/routes/pages.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/utils/utils.dart'; +import 'package:rasadyar_core/core.dart'; + +enum ErrorLocationType { serviceDisabled, permissionDenied, none } + +class ProvinceInspectorRootLogic extends GetxController { + var tokenService = Get.find(); + + late ProvinceInspectorRepository provinceInspectorRepository; + + RxList errorLocationType = RxList(); + RxMap homeExpandedList = RxMap(); + DateTime? _lastBackPressed; + + RxInt currentPage = 0.obs; + + final pages = [ + Navigator( + key: Get.nestedKey(provinceInspectorActionKey), + onGenerateRoute: (settings) { + final page = ProvinceInspectorPages.pages.firstWhere( + (e) => e.name == settings.name, + orElse: () => ProvinceInspectorPages.pages.firstWhere( + (e) => e.name == ProvinceInspectorRoutes.homeProvinceInspector, + ), + ); + + return buildRouteFromGetPage(page); + }, + ), + + ProfilePage(), + ]; + + @override + void onInit() { + super.onInit(); + provinceInspectorRepository = diChicken.get(); + } + + void toggleExpanded(int index) { + if (homeExpandedList.keys.contains(index)) { + homeExpandedList.remove(index); + } else { + homeExpandedList[index] = false; + } + } + + void rootErrorHandler(DioException error) { + handleGeneric(error, () { + tokenService.deleteModuleTokens(Module.chicken); + }); + } + + void changePage(int index) { + currentPage.value = index; + } + + void popBackTaped() async { + 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(); + } + } +} diff --git a/packages/chicken/lib/features/province_inspector/presentation/pages/root/view.dart b/packages/chicken/lib/features/province_inspector/presentation/pages/root/view.dart new file mode 100644 index 0000000..2cf1265 --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/presentation/pages/root/view.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class ProvinceInspectorRootPage extends GetView { + const ProvinceInspectorRootPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isFullScreen: true, + onPopScopTaped: controller.popBackTaped, + child: ObxValue((data) { + return Stack( + children: [ + IndexedStack(children: controller.pages, index: data.value), + Positioned( + right: 0, + left: 0, + bottom: 0, + child: RBottomNavigation( + mainAxisAlignment: MainAxisAlignment.spaceAround, + items: [ + RBottomNavigationItem( + label: 'خانه', + icon: Assets.vec.homeSvg.path, + isSelected: controller.currentPage.value == 0, + onTap: () { + Get.nestedKey( + provinceInspectorActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(0); + }, + ), + RBottomNavigationItem( + label: 'پروفایل', + icon: Assets.vec.profileCircleSvg.path, + isSelected: controller.currentPage.value == 1, + onTap: () { + Get.nestedKey( + provinceInspectorActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(1); + }, + ), + ], + ), + ), + ], + ); + }, controller.currentPage), + ); + } +} diff --git a/packages/chicken/lib/features/province_inspector/presentation/routes/pages.dart b/packages/chicken/lib/features/province_inspector/presentation/routes/pages.dart new file mode 100644 index 0000000..362b831 --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/presentation/routes/pages.dart @@ -0,0 +1,63 @@ +import 'package:rasadyar_chicken/features/province_inspector/presentation/pages/home/logic.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/pages/home/view.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/pages/root/logic.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/pages/root/view.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/pages/active_hatching/view.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/pages/new_inspection/logic.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/pages/new_inspection/view.dart'; +import 'package:rasadyar_chicken/features/province_inspector/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/routes/global_binding.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class ProvinceInspectorPages { + ProvinceInspectorPages._(); + + static List get pages => [ + GetPage( + name: ProvinceInspectorRoutes.initProvinceInspector, + page: () => ProvinceInspectorRootPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ChickenBaseLogic(), fenix: true); + Get.lazyPut(() => ProvinceInspectorRootLogic()); + Get.lazyPut(() => ProvinceInspectorHomeLogic()); + }), + ], + ), + GetPage( + name: ProvinceInspectorRoutes.homeProvinceInspector, + page: () => ProvinceInspectorHomePage(), + middlewares: [AuthMiddleware()], + binding: BindingsBuilder(() { + Get.put(ProvinceInspectorHomeLogic()); + Get.lazyPut(() => ChickenBaseLogic()); + }), + ), + GetPage( + name: ProvinceInspectorRoutes.activeHatchingProvinceInspector, + page: () => ActiveHatchingPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ActiveHatchingLogic()); + }), + ], + ), + GetPage( + name: ProvinceInspectorRoutes.newInspectionProvinceInspector, + page: () => NewInspectionPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => NewInspectionLogic()); + }), + ], + ), + ]; +} diff --git a/packages/chicken/lib/features/province_inspector/presentation/routes/routes.dart b/packages/chicken/lib/features/province_inspector/presentation/routes/routes.dart new file mode 100644 index 0000000..02aae60 --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/presentation/routes/routes.dart @@ -0,0 +1,10 @@ +sealed class ProvinceInspectorRoutes { + ProvinceInspectorRoutes._(); + + static const _base = '/chicken/provinceInspector'; + static const initProvinceInspector = '$_base/'; + static const homeProvinceInspector = '$_base/home'; + static const actionProvinceInspector = '$_base/action'; + static const activeHatchingProvinceInspector = '$_base/activeHatching'; + static const newInspectionProvinceInspector = '$_base/newInspection'; +} diff --git a/packages/chicken/lib/features/province_inspector/province_inspector.dart b/packages/chicken/lib/features/province_inspector/province_inspector.dart new file mode 100644 index 0000000..7f103b9 --- /dev/null +++ b/packages/chicken/lib/features/province_inspector/province_inspector.dart @@ -0,0 +1,3 @@ +export 'data/di/province_inspector_di.dart'; +export 'presentation/routes/routes.dart'; +export 'presentation/routes/pages.dart'; diff --git a/packages/chicken/lib/features/province_operator/data/datasources/remote/province_operator_remote_data_source.dart b/packages/chicken/lib/features/province_operator/data/datasources/remote/province_operator_remote_data_source.dart new file mode 100644 index 0000000..99b5d81 --- /dev/null +++ b/packages/chicken/lib/features/province_operator/data/datasources/remote/province_operator_remote_data_source.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class ProvinceOperatorRemoteDataSource { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/province_operator/data/datasources/remote/province_operator_remote_data_source_impl.dart b/packages/chicken/lib/features/province_operator/data/datasources/remote/province_operator_remote_data_source_impl.dart new file mode 100644 index 0000000..dbdb5fa --- /dev/null +++ b/packages/chicken/lib/features/province_operator/data/datasources/remote/province_operator_remote_data_source_impl.dart @@ -0,0 +1,40 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/province_operator/data/datasources/remote/province_operator_remote_data_source.dart'; +import 'package:rasadyar_core/core.dart'; + +class ProvinceOperatorRemoteDataSourceImpl + implements ProvinceOperatorRemoteDataSource { + final DioRemote _httpClient; + + ProvinceOperatorRemoteDataSourceImpl(this._httpClient); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + var res = await _httpClient.get( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + queryParameters: queryParameters, + fromJson: (json) => PaginationModel.fromJson( + json, + (json) => PoultryScienceReport.fromJson(json as Map), + ), + ); + return res.data; + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + await _httpClient.post( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + data: request.toJson(), + ); + } +} diff --git a/packages/chicken/lib/features/province_operator/data/di/province_operator_di.dart b/packages/chicken/lib/features/province_operator/data/di/province_operator_di.dart new file mode 100644 index 0000000..3da913e --- /dev/null +++ b/packages/chicken/lib/features/province_operator/data/di/province_operator_di.dart @@ -0,0 +1,46 @@ +import 'package:rasadyar_chicken/features/province_operator/data/datasources/remote/province_operator_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/province_operator/data/datasources/remote/province_operator_remote_data_source_impl.dart'; +import 'package:rasadyar_chicken/features/province_operator/data/repositories/province_operator_repository.dart'; +import 'package:rasadyar_chicken/features/province_operator/data/repositories/province_operator_repository_impl.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/repositories/poultry_science_repository.dart'; +import 'package:rasadyar_core/core.dart'; + +/// Setup dependency injection for province_operator feature +Future setupProvinceOperatorDI(GetIt di, DioRemote dioRemote) async { + di.registerLazySingleton( + () => ProvinceOperatorRemoteDataSourceImpl(dioRemote), + ); + + di.registerLazySingleton( + () => ProvinceOperatorRepositoryImpl( + di.get(), + ), + ); + + // Use PoultryScienceRepository for shared functionality + if (!di.isRegistered()) { + // PoultryScienceRepository should already be registered, but just in case + } +} + +/// Re-register province_operator dependencies (used when base URL changes) +Future reRegisterProvinceOperatorDI(GetIt di, DioRemote dioRemote) async { + await reRegister(di, () => ProvinceOperatorRemoteDataSourceImpl(dioRemote)); + await reRegister( + di, + () => ProvinceOperatorRepositoryImpl( + di.get(), + ), + ); +} + +/// Helper function to re-register a dependency +Future reRegister( + GetIt di, + T Function() factory, +) async { + if (di.isRegistered()) { + await di.unregister(); + } + di.registerLazySingleton(factory); +} diff --git a/packages/chicken/lib/features/province_operator/data/repositories/province_operator_repository.dart b/packages/chicken/lib/features/province_operator/data/repositories/province_operator_repository.dart new file mode 100644 index 0000000..0affc82 --- /dev/null +++ b/packages/chicken/lib/features/province_operator/data/repositories/province_operator_repository.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class ProvinceOperatorRepository { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/province_operator/data/repositories/province_operator_repository_impl.dart b/packages/chicken/lib/features/province_operator/data/repositories/province_operator_repository_impl.dart new file mode 100644 index 0000000..84359d3 --- /dev/null +++ b/packages/chicken/lib/features/province_operator/data/repositories/province_operator_repository_impl.dart @@ -0,0 +1,30 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/province_operator/data/datasources/remote/province_operator_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/province_operator/data/repositories/province_operator_repository.dart'; +import 'package:rasadyar_core/core.dart'; + +class ProvinceOperatorRepositoryImpl implements ProvinceOperatorRepository { + final ProvinceOperatorRemoteDataSource _remote; + + ProvinceOperatorRepositoryImpl(this._remote); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + return await _remote.getSubmitInspectionList( + token: token, + queryParameters: queryParameters, + ); + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + return await _remote.submitInspection(token: token, request: request); + } +} diff --git a/packages/chicken/lib/features/province_operator/presentation/pages/active_hatching/logic.dart b/packages/chicken/lib/features/province_operator/presentation/pages/active_hatching/logic.dart new file mode 100644 index 0000000..45e9cf7 --- /dev/null +++ b/packages/chicken/lib/features/province_operator/presentation/pages/active_hatching/logic.dart @@ -0,0 +1,95 @@ +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/repositories/poultry_science_repository.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/root/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingLogic extends GetxController { + ProvinceOperatorRootLogic rootLogic = Get.find(); + BaseLogic baseLogic = Get.find(); + late PoultryScienceRepository poultryScienceRepository; + Rx>> activeHatchingList = + Resource>.loading().obs; + + final RxBool isLoadingMoreList = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + List routesName = ['اقدام', 'جوجه ریزی فعال']; + + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + RxnString searchedValue = RxnString(); + + @override + void onInit() { + super.onInit(); + poultryScienceRepository = diChicken.get(); + } + + @override + void onReady() { + super.onReady(); + getHatchingList(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getHatchingList([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreList.value = true; + } else { + activeHatchingList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => await poultryScienceRepository.getHatchingPoultry( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + queryParams: {'type': 'hatching'}, + role: 'ProvinceOperator', + pageSize: 50, + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + activeHatchingList.value = + Resource>.empty(); + } else { + activeHatchingList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(activeHatchingList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + Future onRefresh() async { + currentPage.value = 1; + await getHatchingList(); + } +} diff --git a/packages/chicken/lib/features/province_operator/presentation/pages/active_hatching/view.dart b/packages/chicken/lib/features/province_operator/presentation/pages/active_hatching/view.dart new file mode 100644 index 0000000..950ecaa --- /dev/null +++ b/packages/chicken/lib/features/province_operator/presentation/pages/active_hatching/view.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet_logic.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingPage extends GetView { + const ActiveHatchingPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasSearch: true, + hasFilter: false, + backId: provinceOperatorActionKey, + routes: controller.routesName, + onSearchChanged: (data) { + controller.searchedValue.value = data; + controller.getHatchingList(); + }, + child: hatchingWidget(), + /*widgets: [ + hatchingWidget() + ],*/ + ); + } + + Widget hatchingWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidget(item), + secondChild: itemListExpandedWidget(item), + labelColor: AppColor.blueLight, + labelIcon: Assets.vec.activeFramSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getHatchingList(true), + ); + }, controller.activeHatchingList); + } + + Container itemListExpandedWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.center, + style: AppFonts.yekan16.copyWith(color: AppColor.greenDark), + ), + Spacer(), + + Visibility( + child: Text( + item.violation == true ? 'پیگیری' : 'عادی', + textAlign: TextAlign.center, + style: AppFonts.yekan10.copyWith(color: AppColor.redDark), + ), + ), + ], + ), + 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: [ + Text( + 'نژاد:${item.breed?.first.breed ?? 'N/A'}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + Text( + ' سن${item.age} (روز)', + + style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal), + ), + Text( + ' دوره جوجه ریزی:${item.period}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ], + ), + ), + + buildRow( + title: 'شماره مجوز جوجه ریزی', + value: item.licenceNumber ?? 'N/A', + ), + buildUnitRow( + title: 'حجم جوجه ریزی', + value: item.quantity.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'مانده در سالن', + value: item.leftOver.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'تلفات', + value: item.losses.separatedByCommaFa, + unit: '(قطعه)', + ), + buildRow( + title: 'دامپزشک فارم', + value: + '${item.vetFarm?.vetFarmFullName}(${item.vetFarm?.vetFarmMobile})', + ), + buildRow( + title: 'شرح بازرسی', + value: item.reportInfo?.image == false + ? 'ارسال تصویر جوجه ریزی فارم ' + : 'تکمیل شده', + titleStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + valueStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + ), + + RElevated( + height: 40.h, + isFullWidth: true, + onPressed: () { + Get.find().setHatchingModel( + item, + ); + Get.bottomSheet( + CreateInspectionBottomSheet(), + isScrollControlled: true, + ignoreSafeArea: false, + ).then((value) { + if (Get.isRegistered()) { + Get.find().clearForm(); + } + }); + }, + child: Text('ثبت بازرسی'), + ), + ], + ), + ); + } + + Widget itemListWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + Text( + item.poultry?.user?.mobile ?? '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: [ + Text( + item.poultry?.unitName ?? 'N/Aaq', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.bgDark), + ), + Text( + item.poultry?.licenceNumber ?? 'N/A', + textAlign: TextAlign.left, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + ], + ), + ), + Expanded( + flex: 1, + child: Assets.vec.scanSvg.svg( + width: 32.w, + height: 32.h, + colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn), + ), + ), + ], + ); + } +} diff --git a/packages/chicken/lib/features/province_operator/presentation/pages/home/logic.dart b/packages/chicken/lib/features/province_operator/presentation/pages/home/logic.dart new file mode 100644 index 0000000..778e68f --- /dev/null +++ b/packages/chicken/lib/features/province_operator/presentation/pages/home/logic.dart @@ -0,0 +1,30 @@ +import 'package:rasadyar_chicken/features/province_operator/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/routes/routes.dart'; +import 'package:rasadyar_core/core.dart'; + +class ProvinceOperatorHomeItem { + final String title; + final String route; + final String icon; + + ProvinceOperatorHomeItem({ + required this.title, + required this.route, + required this.icon, + }); +} + +class ProvinceOperatorHomeLogic extends GetxController { + RxList items = [ + ProvinceOperatorHomeItem( + title: "جوجه ریزی فعال", + route: ProvinceOperatorRoutes.activeHatchingProvinceOperator, + icon: Assets.vec.activeFramSvg.path, + ), + ProvinceOperatorHomeItem( + title: "بازرسی مزارع طیور", + route: ProvinceOperatorRoutes.newInspectionProvinceOperator, + icon: Assets.vec.activeFramSvg.path, + ), + ].obs; +} diff --git a/packages/chicken/lib/features/province_operator/presentation/pages/home/view.dart b/packages/chicken/lib/features/province_operator/presentation/pages/home/view.dart new file mode 100644 index 0000000..1e52884 --- /dev/null +++ b/packages/chicken/lib/features/province_operator/presentation/pages/home/view.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class ProvinceOperatorHomePage extends GetView { + ProvinceOperatorHomePage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isBase: true, + hasNews: true, + hasNotification: true, + child: gridWidget(), + ); + } + + Widget gridWidget() { + return ObxValue((data) { + return GridView.builder( + physics: BouncingScrollPhysics(), + padding: EdgeInsets.symmetric(vertical: 18.h, horizontal: 32.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 24.h, + crossAxisSpacing: 24.w, + ), + itemCount: data.length, + hitTestBehavior: HitTestBehavior.opaque, + itemBuilder: (BuildContext context, int index) { + var item = data[index]; + return GlassMorphismCardIcon( + title: item.title, + vecIcon: item.icon, + onTap: () async { + Get.toNamed(item.route, id: provinceOperatorActionKey); + }, + ); + }, + ); + }, controller.items); + } +} diff --git a/packages/chicken/lib/features/province_operator/presentation/pages/new_inspection/logic.dart b/packages/chicken/lib/features/province_operator/presentation/pages/new_inspection/logic.dart new file mode 100644 index 0000000..3909ea3 --- /dev/null +++ b/packages/chicken/lib/features/province_operator/presentation/pages/new_inspection/logic.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/root/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class NewInspectionLogic extends GetxController { + BaseLogic baseLogic = Get.find(); + + Rx>> submitInspectionList = + Resource>.loading().obs; + + ProvinceOperatorRootLogic rootLogic = Get.find(); + + final RxBool isLoadingMoreAllocationsMade = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + RxList pickedImages = [].obs; + final List _multiPartPickedImages = []; + + RxBool isOnUpload = false.obs; + + RxDouble presentUpload = 0.0.obs; + RxList routesName = RxList(); + RxInt selectedSegmentIndex = 0.obs; + + RxnString searchedValue = RxnString(); + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + + @override + void onInit() { + super.onInit(); + + routesName.value = ['اقدام'].toList(); + + ever(selectedSegmentIndex, (callback) { + routesName.removeLast(); + routesName.add(callback == 0 ? 'بازرسی' : 'بایگانی'); + }); + } + + @override + void onReady() { + super.onReady(); + + getReport(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getReport([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreAllocationsMade.value = true; + } else { + submitInspectionList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => + await rootLogic.provinceOperatorRepository.getSubmitInspectionList( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + role: 'ProvinceOperator', + pageSize: 50, + search: 'filter', + + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + submitInspectionList.value = + Resource>.empty(); + } else { + submitInspectionList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(submitInspectionList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + Future pickImages() async { + determineCurrentPosition(); + var tmp = await pickCameraImage(); + if (tmp?.path != null && pickedImages.length < 7) { + pickedImages.add(tmp!); + } + } + + void removeImage(int index) { + pickedImages.removeAt(index); + } + + void closeBottomSheet() { + Get.back(); + } + + double calculateUploadProgress({required int sent, required int total}) { + if (total != 0) { + double progress = (sent * 100 / total) / 100; + return progress; + } else { + return 0.0; + } + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + void setSearchValue(String? data) { + dLog('Search Value: $data'); + searchedValue.value = data?.trim(); + getReport(); + } + + Future onRefresh() async { + currentPage.value = 1; + await getReport(); + } + + String getStatus(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return 'در حال بررسی'; + } + return status; + } + + Color getStatusColor(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return AppColor.yellowNormal; + } + // می‌توانید منطق رنگ را بر اساس inspectionStatus تنظیم کنید + return AppColor.greenNormal; + } +} diff --git a/packages/chicken/lib/features/province_operator/presentation/pages/new_inspection/view.dart b/packages/chicken/lib/features/province_operator/presentation/pages/new_inspection/view.dart new file mode 100644 index 0000000..00ee4f6 --- /dev/null +++ b/packages/chicken/lib/features/province_operator/presentation/pages/new_inspection/view.dart @@ -0,0 +1,1134 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class NewInspectionPage extends GetView { + const NewInspectionPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasBack: true, + hasFilter: true, + hasSearch: true, + onFilterTap: () { + Get.bottomSheet(filterBottomSheet()); + }, + onRefresh: controller.onRefresh, + onSearchChanged: (data) => controller.setSearchValue(data), + backId: provinceOperatorActionKey, + routesWidget: ContainerBreadcrumb(rxRoutes: controller.routesName), + child: Column(children: [reportWidget()]), + ); + } + + Widget reportWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidgetReport(item), + secondChild: itemListExpandedWidgetReport(item), + labelColor: AppColor.greenLight, + labelIcon: Assets.vec.cubeSearchSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getReport(true), + ); + }, controller.submitInspectionList), + ); + } + + Widget itemListExpandedWidgetReport(PoultryScienceReport item) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + spacing: 8, + children: [ + buildRow( + title: 'وضعیت بازرسی', + value: controller.getStatus(item), + titleStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + valueStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + ), + if (item.hatching?.poultry?.unitName != null) + buildRow( + title: 'مرغداری', + value: item.hatching?.poultry?.unitName ?? '-', + ), + if (item.hatching?.id != null) + buildRow( + title: 'شناسه جوجه‌ریزی', + value: item.hatching!.id.toString(), + ), + + if (item + .reportInformation + ?.technicalOfficer + ?.technicalHealthOfficer != + null) + buildRow( + title: 'کارشناس بهداشت', + value: + item + .reportInformation! + .technicalOfficer! + .technicalHealthOfficer ?? + '-', + ), + if (item + .reportInformation + ?.technicalOfficer + ?.technicalEngineeringOfficer != + null) + buildRow( + title: 'کارشناس فنی', + value: + item + .reportInformation! + .technicalOfficer! + .technicalEngineeringOfficer ?? + '-', + ), + if (item.reportInformation?.casualties?.normalLosses != null || + item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات عادی', + value: (item.reportInformation?.casualties?.normalLosses ?? 0) + .toString(), + unit: '(قطعه)', + ), + if (item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات غیرعادی', + value: item.reportInformation!.casualties!.abnormalLosses + .toString(), + unit: '(قطعه)', + ), + + if (item.reportInformation?.inspectionNotes != null && + item.reportInformation!.inspectionNotes!.isNotEmpty) + buildRow( + title: 'یادداشت بازرسی', + value: item.reportInformation!.inspectionNotes ?? '-', + ), + + RElevated( + text: 'جزییات', + isFullWidth: true, + width: 150.w, + height: 40.h, + onPressed: () { + showDetailsBottomSheet(item); + }, + textStyle: AppFonts.yekan16.copyWith(color: Colors.white), + backgroundColor: AppColor.greenNormal, + ), + ], + ), + ); + } + + Row itemListWidgetReport(PoultryScienceReport item) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox(width: 20), + Expanded( + flex: 2, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 5, + children: [ + Text( + 'شناسه جوجه‌ریزی: ${item.hatching?.id ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + + Text( + item.createDate?.toJalali.formatCompactDate() ?? '-', + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + Expanded( + flex: 3, + child: Column( + spacing: 5, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'مرغداری: ${item.hatching?.poultry?.unitName ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + if (item.reportInformation?.inspectionStatus != null) + Text( + 'وضعیت بازرسی: ${item.reportInformation?.inspectionStatus ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + ], + ); + } + + void showDetailsBottomSheet(PoultryScienceReport item) { + Get.bottomSheet( + isScrollControlled: true, + BaseBottomSheet( + height: Get.height * 0.8, + rootChild: DetailsBottomSheetWidget(item: item), + ), + ); + } + + 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.getReport(); + Get.back(); + }, + height: 40, + ), + ], + ), + ); + } +} + +class DetailsBottomSheetWidget extends StatefulWidget { + final PoultryScienceReport item; + + const DetailsBottomSheetWidget({super.key, required this.item}); + + @override + State createState() => + _DetailsBottomSheetWidgetState(); +} + +class _DetailsBottomSheetWidgetState extends State { + int selectedTabIndex = 0; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 10, + ), + child: Text( + 'جزییات', + style: AppFonts.yekan18Bold.copyWith( + color: AppColor.iconColor, + ), + ), + ), + ], + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + tabBarWidget( + [ + 'اطلاعات کلی', + 'شرایط سالن', + 'تلفات', + 'کارشناس', + 'نهاده', + 'زیرساخت', + 'نیروی انسانی', + 'تسهیلات', + ], + selectedTabIndex, + (index) => setState(() => selectedTabIndex = index), + ), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _buildTableContent(), + ), + ), + ), + ], + ); + } + + Widget _buildTableContent() { + switch (selectedTabIndex) { + case 0: + return generalInfoTable(); + case 1: + return generalConditionHallTable(); + case 2: + return casualtiesTable(); + case 3: + return technicalOfficerTable(); + case 4: + return inputStatusTable(); + case 5: + return infrastructureEnergyTable(); + case 6: + return hrTable(); + case 7: + return facilitiesTable(); + default: + return generalInfoTable(); + } + } + + Widget technicalOfficerTable() { + final officer = widget.item.reportInformation?.technicalOfficer; + if (officer == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'کارشناس فنی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'کارشناس بهداشت', + value: officer.technicalHealthOfficer ?? '-', + ), + rTableRow( + title: 'کارشناس فنی', + value: officer.technicalEngineeringOfficer ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget generalInfoTable() { + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'اطلاعات کلی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت بازرسی', + value: + widget.item.reportInformation?.inspectionStatus ?? + widget.item.state ?? + '-', + ), + rTableRow( + title: 'یادداشت بازرسی', + value: widget.item.reportInformation?.inspectionNotes ?? '-', + ), + rTableRow(title: 'نقش', value: widget.item.reporterRole ?? '-'), + if (widget.item.lat != null && widget.item.log != null) + rTableRow( + title: 'موقعیت', + value: '${widget.item.lat}, ${widget.item.log}', + ), + if (widget.item.hatching?.id != null) + rTableRow( + title: 'شناسه جوجه ریزی', + value: widget.item.hatching!.id.toString(), + ), + ], + ), + ), + ], + ); + } + + Widget generalConditionHallTable() { + final hall = widget.item.reportInformation?.generalConditionHall; + if (hall == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'شرایط عمومی سالن', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow(title: 'وضعیت سلامت', value: hall.healthStatus ?? '-'), + rTableRow( + title: 'وضعیت تهویه', + value: hall.ventilationStatus ?? '-', + ), + rTableRow(title: 'وضعیت بستر', value: hall.bedCondition ?? '-'), + rTableRow(title: 'دما', value: hall.temperature ?? '-'), + rTableRow( + title: 'منبع آب آشامیدنی', + value: hall.drinkingWaterSource ?? '-', + ), + rTableRow( + title: 'کیفیت آب آشامیدنی', + value: hall.drinkingWaterQuality ?? '-', + ), + ], + ), + ), + if (hall.images != null && hall.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: hall.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(hall.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(hall.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget casualtiesTable() { + final casualties = widget.item.reportInformation?.casualties; + if (casualties == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تلفات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (casualties.normalLosses != null) + rTableRow( + title: 'تلفات عادی', + value: casualties.normalLosses.toString(), + ), + if (casualties.abnormalLosses != null) + rTableRow( + title: 'تلفات غیرعادی', + value: casualties.abnormalLosses.toString(), + ), + rTableRow( + title: 'منبع جوجه ریزی', + value: casualties.sourceOfHatching ?? '-', + ), + rTableRow( + title: 'علت تلفات غیرعادی', + value: casualties.causeAbnormalLosses ?? '-', + ), + rTableRow( + title: 'نوع بیماری', + value: casualties.typeDisease ?? '-', + ), + rTableRow( + title: 'نمونه‌برداری انجام شده', + value: casualties.samplingDone == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع نمونه‌برداری', + value: casualties.typeSampling ?? '-', + ), + ], + ), + ), + if (casualties.images != null && casualties.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: casualties.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(casualties.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(casualties.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget inputStatusTable() { + final inputStatus = widget.item.reportInformation?.inputStatus; + if (inputStatus == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'وضعیت نهاده', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت نهاده', + value: inputStatus.inputStatus ?? '-', + ), + rTableRow( + title: 'نام شرکت', + value: inputStatus.companyName ?? '-', + ), + rTableRow( + title: 'کد پیگیری', + value: inputStatus.trackingCode ?? '-', + ), + rTableRow( + title: 'نوع دانه', + value: inputStatus.typeOfGrain ?? '-', + ), + rTableRow( + title: 'موجودی در انبار', + value: inputStatus.inventoryInWarehouse ?? '-', + ), + rTableRow( + title: 'موجودی تا بازدید', + value: inputStatus.inventoryUntilVisit ?? '-', + ), + rTableRow( + title: 'درجه دانه', + value: inputStatus.gradeGrain ?? '-', + ), + ], + ), + ), + if (inputStatus.images != null && inputStatus.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: inputStatus.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(inputStatus.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(inputStatus.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget infrastructureEnergyTable() { + final infra = widget.item.reportInformation?.infrastructureEnergy; + if (infra == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'زیرساخت و انرژی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'نوع ژنراتور', + value: infra.generatorType ?? '-', + ), + rTableRow( + title: 'مدل ژنراتور', + value: infra.generatorModel ?? '-', + ), + rTableRow( + title: 'تعداد ژنراتور', + value: infra.generatorCount ?? '-', + ), + rTableRow( + title: 'ظرفیت ژنراتور', + value: infra.generatorCapacity ?? '-', + ), + rTableRow(title: 'نوع سوخت', value: infra.fuelType ?? '-'), + rTableRow( + title: 'عملکرد ژنراتور', + value: infra.generatorPerformance ?? '-', + ), + rTableRow( + title: 'موجودی سوخت اضطراری', + value: infra.emergencyFuelInventory ?? '-', + ), + rTableRow( + title: 'تاریخچه قطع برق', + value: infra.hasPowerCutHistory == true ? 'بله' : 'خیر', + ), + if (infra.hasPowerCutHistory == true) ...[ + rTableRow( + title: 'مدت قطع برق', + value: infra.powerCutDuration ?? '-', + ), + rTableRow( + title: 'ساعت قطع برق', + value: infra.powerCutHour ?? '-', + ), + ], + rTableRow( + title: 'یادداشت اضافی', + value: infra.additionalNotes ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget hrTable() { + final hr = widget.item.reportInformation?.hr; + if (hr == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'نیروی انسانی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (hr.numberEmployed != null) + rTableRow( + title: 'تعداد شاغلین', + value: hr.numberEmployed.toString(), + ), + if (hr.numberIndigenous != null) + rTableRow( + title: 'تعداد بومی', + value: hr.numberIndigenous.toString(), + ), + if (hr.numberNonIndigenous != null) + rTableRow( + title: 'تعداد غیربومی', + value: hr.numberNonIndigenous.toString(), + ), + rTableRow( + title: 'وضعیت قرارداد', + value: hr.contractStatus ?? '-', + ), + rTableRow( + title: 'آموزش دیده', + value: hr.trained == true ? 'بله' : 'خیر', + ), + ], + ), + ), + ], + ); + } + + Widget facilitiesTable() { + final facilities = widget.item.reportInformation?.facilities; + if (facilities == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تسهیلات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'دارای تسهیلات', + value: facilities.hasFacilities == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع تسهیلات', + value: facilities.typeOfFacility ?? '-', + ), + if (facilities.amount != null) + rTableRow(title: 'مبلغ', value: facilities.amount.toString()), + rTableRow(title: 'تاریخ', value: facilities.date ?? '-'), + rTableRow( + title: 'وضعیت بازپرداخت', + value: facilities.repaymentStatus ?? '-', + ), + rTableRow( + title: 'درخواست تسهیلات', + value: facilities.requestFacilities ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget tabBarWidget( + List tabs, + int selectedIndex, + Function(int) onTabSelected, + ) { + return SizedBox( + height: 40.h, + width: Get.width, + child: Column( + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + reverse: true, + child: Row( + children: [ + ...tabs.map( + (tab) => GestureDetector( + onTap: () => onTabSelected(tabs.indexOf(tab)), + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 11, + ), + decoration: BoxDecoration( + border: tab == tabs[selectedIndex] + ? Border( + bottom: BorderSide( + color: AppColor.blueNormalOld, + width: 3, + ), + ) + : null, + ), + child: Text( + tab, + style: AppFonts.yekan12Bold.copyWith( + color: tab == tabs[selectedIndex] + ? AppColor.blueNormalOld + : AppColor.mediumGrey, + ), + ), + ), + ), + ), + ], + ), + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + ], + ), + ); + } + + Row rTableRow({String? title, String? value}) { + return Row( + children: [ + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + color: AppColor.bgLight2, + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + title ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.iconColor), + ), + ), + ), + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + value ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + ), + ), + ], + ); + } + + void showImageDialog(List images, int initialIndex) { + final pageController = PageController(initialPage: initialIndex); + final currentIndex = initialIndex.obs; + + Get.dialog( + Dialog( + backgroundColor: Colors.transparent, + insetPadding: EdgeInsets.zero, + child: Container( + width: Get.width, + height: Get.height, + color: Colors.black, + child: Stack( + children: [ + PageView.builder( + controller: pageController, + itemCount: images.length, + onPageChanged: (index) { + currentIndex.value = index; + }, + itemBuilder: (context, index) { + return InteractiveViewer( + minScale: 0.5, + maxScale: 4.0, + child: Center( + child: Image.network( + images[index], + fit: BoxFit.contain, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + color: Colors.white, + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Center( + child: Icon( + Icons.error, + color: Colors.white, + size: 50, + ), + ); + }, + ), + ), + ); + }, + ), + Positioned( + top: 40, + right: 16, + child: IconButton( + icon: Icon(Icons.close, color: Colors.white, size: 30), + onPressed: () => Get.back(), + ), + ), + if (images.length > 1) + Positioned( + bottom: 20, + left: 0, + right: 0, + child: Center( + child: Obx( + () => Container( + padding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '${currentIndex.value + 1} / ${images.length}', + style: TextStyle(color: Colors.white, fontSize: 14), + ), + ), + ), + ), + ), + ], + ), + ), + ), + barrierDismissible: true, + ); + } +} diff --git a/packages/chicken/lib/features/province_operator/presentation/pages/root/logic.dart b/packages/chicken/lib/features/province_operator/presentation/pages/root/logic.dart new file mode 100644 index 0000000..0f9f14a --- /dev/null +++ b/packages/chicken/lib/features/province_operator/presentation/pages/root/logic.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/province_operator/data/repositories/province_operator_repository.dart'; +import 'package:rasadyar_chicken/features/common/presentation/page/profile/view.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/routes/pages.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/utils/utils.dart'; +import 'package:rasadyar_core/core.dart'; + +enum ErrorLocationType { serviceDisabled, permissionDenied, none } + +class ProvinceOperatorRootLogic extends GetxController { + var tokenService = Get.find(); + + late ProvinceOperatorRepository provinceOperatorRepository; + + RxList errorLocationType = RxList(); + RxMap homeExpandedList = RxMap(); + DateTime? _lastBackPressed; + + RxInt currentPage = 0.obs; + + final pages = [ + Navigator( + key: Get.nestedKey(provinceOperatorActionKey), + onGenerateRoute: (settings) { + final page = ProvinceOperatorPages.pages.firstWhere( + (e) => e.name == settings.name, + orElse: () => ProvinceOperatorPages.pages.firstWhere( + (e) => e.name == ProvinceOperatorRoutes.homeProvinceOperator, + ), + ); + + return buildRouteFromGetPage(page); + }, + ), + + ProfilePage(), + ]; + + @override + void onInit() { + super.onInit(); + provinceOperatorRepository = diChicken.get(); + } + + void toggleExpanded(int index) { + if (homeExpandedList.keys.contains(index)) { + homeExpandedList.remove(index); + } else { + homeExpandedList[index] = false; + } + } + + void rootErrorHandler(DioException error) { + handleGeneric(error, () { + tokenService.deleteModuleTokens(Module.chicken); + }); + } + + void changePage(int index) { + currentPage.value = index; + } + + void popBackTaped() async { + 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(); + } + } +} diff --git a/packages/chicken/lib/features/province_operator/presentation/pages/root/view.dart b/packages/chicken/lib/features/province_operator/presentation/pages/root/view.dart new file mode 100644 index 0000000..90b8f2b --- /dev/null +++ b/packages/chicken/lib/features/province_operator/presentation/pages/root/view.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class ProvinceOperatorRootPage extends GetView { + const ProvinceOperatorRootPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isFullScreen: true, + onPopScopTaped: controller.popBackTaped, + child: ObxValue((data) { + return Stack( + children: [ + IndexedStack(children: controller.pages, index: data.value), + Positioned( + right: 0, + left: 0, + bottom: 0, + child: RBottomNavigation( + mainAxisAlignment: MainAxisAlignment.spaceAround, + items: [ + + + RBottomNavigationItem( + label: 'خانه', + icon: Assets.vec.homeSvg.path, + isSelected: controller.currentPage.value == 0, + onTap: () { + Get.nestedKey( + provinceOperatorActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(0); + }, + ), + RBottomNavigationItem( + label: 'پروفایل', + icon: Assets.vec.profileCircleSvg.path, + isSelected: controller.currentPage.value == 1, + onTap: () { + Get.nestedKey( + provinceOperatorActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(1); + }, + ), + ], + ), + ), + ], + ); + }, controller.currentPage), + ); + } +} diff --git a/packages/chicken/lib/features/province_operator/presentation/routes/pages.dart b/packages/chicken/lib/features/province_operator/presentation/routes/pages.dart new file mode 100644 index 0000000..9d0f34f --- /dev/null +++ b/packages/chicken/lib/features/province_operator/presentation/routes/pages.dart @@ -0,0 +1,76 @@ +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/home/logic.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/home/view.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/root/logic.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/root/view.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/home/logic.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/home/view.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/active_hatching/view.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/new_inspection/logic.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/new_inspection/view.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/routes/global_binding.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class ProvinceOperatorPages { + ProvinceOperatorPages._(); + + static List get pages => [ + GetPage( + name: ProvinceOperatorRoutes.initProvinceOperator, + page: () => ProvinceOperatorRootPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ChickenBaseLogic(), fenix: true); + Get.lazyPut(() => ProvinceOperatorRootLogic()); + Get.lazyPut(() => ProvinceOperatorHomeLogic()); + }), + ], + ), + GetPage( + name: ProvinceOperatorRoutes.homeProvinceOperator, + page: () => ProvinceOperatorHomePage(), + middlewares: [AuthMiddleware()], + binding: BindingsBuilder(() { + Get.put(ProvinceOperatorHomeLogic()); + Get.lazyPut(() => ChickenBaseLogic()); + }), + ), + GetPage( + name: ProvinceOperatorRoutes.actionProvinceOperator, + page: () => ProvinceOperatorHomePage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ProvinceOperatorHomeLogic()); + }), + ], + ), + GetPage( + name: ProvinceOperatorRoutes.activeHatchingProvinceOperator, + page: () => ActiveHatchingPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ActiveHatchingLogic()); + }), + ], + ), + GetPage( + name: ProvinceOperatorRoutes.newInspectionProvinceOperator, + page: () => NewInspectionPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => NewInspectionLogic()); + }), + ], + ), + ]; +} diff --git a/packages/chicken/lib/features/province_operator/presentation/routes/routes.dart b/packages/chicken/lib/features/province_operator/presentation/routes/routes.dart new file mode 100644 index 0000000..be47597 --- /dev/null +++ b/packages/chicken/lib/features/province_operator/presentation/routes/routes.dart @@ -0,0 +1,10 @@ +sealed class ProvinceOperatorRoutes { + ProvinceOperatorRoutes._(); + + static const _base = '/chicken/provinceOperator'; + static const initProvinceOperator = '$_base/'; + static const homeProvinceOperator = '$_base/home'; + static const actionProvinceOperator = '$_base/action'; + static const activeHatchingProvinceOperator = '$_base/activeHatching'; + static const newInspectionProvinceOperator = '$_base/newInspection'; +} diff --git a/packages/chicken/lib/features/province_operator/province_operator.dart b/packages/chicken/lib/features/province_operator/province_operator.dart new file mode 100644 index 0000000..28e0af1 --- /dev/null +++ b/packages/chicken/lib/features/province_operator/province_operator.dart @@ -0,0 +1,3 @@ +export 'data/di/province_operator_di.dart'; +export 'presentation/routes/routes.dart'; +export 'presentation/routes/pages.dart'; diff --git a/packages/chicken/lib/features/province_supervisor/data/datasources/remote/province_supervisor_remote_data_source.dart b/packages/chicken/lib/features/province_supervisor/data/datasources/remote/province_supervisor_remote_data_source.dart new file mode 100644 index 0000000..f42a0fb --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/data/datasources/remote/province_supervisor_remote_data_source.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class ProvinceSupervisorRemoteDataSource { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/province_supervisor/data/datasources/remote/province_supervisor_remote_data_source_impl.dart b/packages/chicken/lib/features/province_supervisor/data/datasources/remote/province_supervisor_remote_data_source_impl.dart new file mode 100644 index 0000000..9133bee --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/data/datasources/remote/province_supervisor_remote_data_source_impl.dart @@ -0,0 +1,40 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/data/datasources/remote/province_supervisor_remote_data_source.dart'; +import 'package:rasadyar_core/core.dart'; + +class ProvinceSupervisorRemoteDataSourceImpl + implements ProvinceSupervisorRemoteDataSource { + final DioRemote _httpClient; + + ProvinceSupervisorRemoteDataSourceImpl(this._httpClient); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + var res = await _httpClient.get( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + queryParameters: queryParameters, + fromJson: (json) => PaginationModel.fromJson( + json, + (json) => PoultryScienceReport.fromJson(json as Map), + ), + ); + return res.data; + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + await _httpClient.post( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + data: request.toJson(), + ); + } +} diff --git a/packages/chicken/lib/features/province_supervisor/data/di/province_supervisor_di.dart b/packages/chicken/lib/features/province_supervisor/data/di/province_supervisor_di.dart new file mode 100644 index 0000000..7b9b87e --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/data/di/province_supervisor_di.dart @@ -0,0 +1,40 @@ +import 'package:rasadyar_chicken/features/province_supervisor/data/datasources/remote/province_supervisor_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/data/datasources/remote/province_supervisor_remote_data_source_impl.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/data/repositories/province_supervisor_repository.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/data/repositories/province_supervisor_repository_impl.dart'; +import 'package:rasadyar_core/core.dart'; + +/// Setup dependency injection for province_supervisor feature +Future setupProvinceSupervisorDI(GetIt di, DioRemote dioRemote) async { + di.registerLazySingleton( + () => ProvinceSupervisorRemoteDataSourceImpl(dioRemote), + ); + + di.registerLazySingleton( + () => ProvinceSupervisorRepositoryImpl( + di.get()), + ); +} + +/// Re-register province_supervisor dependencies (used when base URL changes) +Future reRegisterProvinceSupervisorDI( + GetIt di, DioRemote dioRemote) async { + await reRegister( + di, () => ProvinceSupervisorRemoteDataSourceImpl(dioRemote)); + await reRegister( + di, + () => ProvinceSupervisorRepositoryImpl( + di.get()), + ); +} + +/// Helper function to re-register a dependency +Future reRegister( + GetIt di, + T Function() factory, +) async { + if (di.isRegistered()) { + await di.unregister(); + } + di.registerLazySingleton(factory); +} diff --git a/packages/chicken/lib/features/province_supervisor/data/repositories/province_supervisor_repository.dart b/packages/chicken/lib/features/province_supervisor/data/repositories/province_supervisor_repository.dart new file mode 100644 index 0000000..4961278 --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/data/repositories/province_supervisor_repository.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class ProvinceSupervisorRepository { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/province_supervisor/data/repositories/province_supervisor_repository_impl.dart b/packages/chicken/lib/features/province_supervisor/data/repositories/province_supervisor_repository_impl.dart new file mode 100644 index 0000000..0a2a420 --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/data/repositories/province_supervisor_repository_impl.dart @@ -0,0 +1,30 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/data/datasources/remote/province_supervisor_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/data/repositories/province_supervisor_repository.dart'; +import 'package:rasadyar_core/core.dart'; + +class ProvinceSupervisorRepositoryImpl implements ProvinceSupervisorRepository { + final ProvinceSupervisorRemoteDataSource _remote; + + ProvinceSupervisorRepositoryImpl(this._remote); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + return await _remote.getSubmitInspectionList( + token: token, + queryParameters: queryParameters, + ); + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + return await _remote.submitInspection(token: token, request: request); + } +} diff --git a/packages/chicken/lib/features/province_supervisor/presentation/pages/active_hatching/logic.dart b/packages/chicken/lib/features/province_supervisor/presentation/pages/active_hatching/logic.dart new file mode 100644 index 0000000..45e9cf7 --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/presentation/pages/active_hatching/logic.dart @@ -0,0 +1,95 @@ +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/repositories/poultry_science_repository.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/root/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingLogic extends GetxController { + ProvinceOperatorRootLogic rootLogic = Get.find(); + BaseLogic baseLogic = Get.find(); + late PoultryScienceRepository poultryScienceRepository; + Rx>> activeHatchingList = + Resource>.loading().obs; + + final RxBool isLoadingMoreList = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + List routesName = ['اقدام', 'جوجه ریزی فعال']; + + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + RxnString searchedValue = RxnString(); + + @override + void onInit() { + super.onInit(); + poultryScienceRepository = diChicken.get(); + } + + @override + void onReady() { + super.onReady(); + getHatchingList(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getHatchingList([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreList.value = true; + } else { + activeHatchingList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => await poultryScienceRepository.getHatchingPoultry( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + queryParams: {'type': 'hatching'}, + role: 'ProvinceOperator', + pageSize: 50, + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + activeHatchingList.value = + Resource>.empty(); + } else { + activeHatchingList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(activeHatchingList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + Future onRefresh() async { + currentPage.value = 1; + await getHatchingList(); + } +} diff --git a/packages/chicken/lib/features/province_supervisor/presentation/pages/active_hatching/view.dart b/packages/chicken/lib/features/province_supervisor/presentation/pages/active_hatching/view.dart new file mode 100644 index 0000000..950ecaa --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/presentation/pages/active_hatching/view.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/province_operator/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet_logic.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingPage extends GetView { + const ActiveHatchingPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasSearch: true, + hasFilter: false, + backId: provinceOperatorActionKey, + routes: controller.routesName, + onSearchChanged: (data) { + controller.searchedValue.value = data; + controller.getHatchingList(); + }, + child: hatchingWidget(), + /*widgets: [ + hatchingWidget() + ],*/ + ); + } + + Widget hatchingWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidget(item), + secondChild: itemListExpandedWidget(item), + labelColor: AppColor.blueLight, + labelIcon: Assets.vec.activeFramSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getHatchingList(true), + ); + }, controller.activeHatchingList); + } + + Container itemListExpandedWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.center, + style: AppFonts.yekan16.copyWith(color: AppColor.greenDark), + ), + Spacer(), + + Visibility( + child: Text( + item.violation == true ? 'پیگیری' : 'عادی', + textAlign: TextAlign.center, + style: AppFonts.yekan10.copyWith(color: AppColor.redDark), + ), + ), + ], + ), + 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: [ + Text( + 'نژاد:${item.breed?.first.breed ?? 'N/A'}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + Text( + ' سن${item.age} (روز)', + + style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal), + ), + Text( + ' دوره جوجه ریزی:${item.period}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ], + ), + ), + + buildRow( + title: 'شماره مجوز جوجه ریزی', + value: item.licenceNumber ?? 'N/A', + ), + buildUnitRow( + title: 'حجم جوجه ریزی', + value: item.quantity.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'مانده در سالن', + value: item.leftOver.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'تلفات', + value: item.losses.separatedByCommaFa, + unit: '(قطعه)', + ), + buildRow( + title: 'دامپزشک فارم', + value: + '${item.vetFarm?.vetFarmFullName}(${item.vetFarm?.vetFarmMobile})', + ), + buildRow( + title: 'شرح بازرسی', + value: item.reportInfo?.image == false + ? 'ارسال تصویر جوجه ریزی فارم ' + : 'تکمیل شده', + titleStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + valueStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + ), + + RElevated( + height: 40.h, + isFullWidth: true, + onPressed: () { + Get.find().setHatchingModel( + item, + ); + Get.bottomSheet( + CreateInspectionBottomSheet(), + isScrollControlled: true, + ignoreSafeArea: false, + ).then((value) { + if (Get.isRegistered()) { + Get.find().clearForm(); + } + }); + }, + child: Text('ثبت بازرسی'), + ), + ], + ), + ); + } + + Widget itemListWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + Text( + item.poultry?.user?.mobile ?? '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: [ + Text( + item.poultry?.unitName ?? 'N/Aaq', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.bgDark), + ), + Text( + item.poultry?.licenceNumber ?? 'N/A', + textAlign: TextAlign.left, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + ], + ), + ), + Expanded( + flex: 1, + child: Assets.vec.scanSvg.svg( + width: 32.w, + height: 32.h, + colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn), + ), + ), + ], + ); + } +} diff --git a/packages/chicken/lib/features/province_supervisor/presentation/pages/home/logic.dart b/packages/chicken/lib/features/province_supervisor/presentation/pages/home/logic.dart new file mode 100644 index 0000000..61d4abe --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/presentation/pages/home/logic.dart @@ -0,0 +1,30 @@ +import 'package:rasadyar_chicken/features/province_supervisor/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/routes/routes.dart'; +import 'package:rasadyar_core/core.dart'; + +class ProvinceSupervisorActionItem { + final String title; + final String route; + final String icon; + + ProvinceSupervisorActionItem({ + required this.title, + required this.route, + required this.icon, + }); +} + +class ProvinceSupervisorHomeLogic extends GetxController { + RxList items = [ + ProvinceSupervisorActionItem( + title: "جوجه ریزی فعال", + route: ProvinceSupervisorRoutes.activeHatchingProvinceSupervisor, + icon: Assets.vec.activeFramSvg.path, + ), + ProvinceSupervisorActionItem( + title: "بازرسی مزارع طیور", + route: ProvinceSupervisorRoutes.newInspectionProvinceSupervisor, + icon: Assets.vec.activeFramSvg.path, + ), + ].obs; +} diff --git a/packages/chicken/lib/features/province_supervisor/presentation/pages/home/view.dart b/packages/chicken/lib/features/province_supervisor/presentation/pages/home/view.dart new file mode 100644 index 0000000..ce8bb6b --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/presentation/pages/home/view.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class ProvinceSupervisorHomePage extends GetView { + ProvinceSupervisorHomePage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isBase: true, + hasNews: true, + hasNotification: true, + child: gridWidget(), + ); + } + + Widget gridWidget() { + return ObxValue((data) { + return GridView.builder( + physics: BouncingScrollPhysics(), + padding: EdgeInsets.symmetric(vertical: 18.h, horizontal: 32.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 24.h, + crossAxisSpacing: 24.w, + ), + itemCount: data.length, + hitTestBehavior: HitTestBehavior.opaque, + itemBuilder: (BuildContext context, int index) { + var item = data[index]; + return GlassMorphismCardIcon( + title: item.title, + vecIcon: item.icon, + onTap: () async { + Get.toNamed(item.route, id: provinceSupervisorActionKey); + }, + ); + }, + ); + }, controller.items); + } +} diff --git a/packages/chicken/lib/features/province_supervisor/presentation/pages/new_inspection/logic.dart b/packages/chicken/lib/features/province_supervisor/presentation/pages/new_inspection/logic.dart new file mode 100644 index 0000000..c93deb0 --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/presentation/pages/new_inspection/logic.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/presentation/pages/root/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class NewInspectionLogic extends GetxController { + BaseLogic baseLogic = Get.find(); + + Rx>> submitInspectionList = + Resource>.loading().obs; + + ProvinceSupervisorRootLogic rootLogic = + Get.find(); + + final RxBool isLoadingMoreAllocationsMade = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + RxList pickedImages = [].obs; + final List _multiPartPickedImages = []; + + RxBool isOnUpload = false.obs; + + RxDouble presentUpload = 0.0.obs; + RxList routesName = RxList(); + RxInt selectedSegmentIndex = 0.obs; + + RxnString searchedValue = RxnString(); + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + + @override + void onInit() { + super.onInit(); + + routesName.value = ['اقدام'].toList(); + + ever(selectedSegmentIndex, (callback) { + routesName.removeLast(); + routesName.add(callback == 0 ? 'بازرسی' : 'بایگانی'); + }); + } + + @override + void onReady() { + super.onReady(); + + getReport(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getReport([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreAllocationsMade.value = true; + } else { + submitInspectionList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => + await rootLogic.provinceSupervisorRepository.getSubmitInspectionList( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + role: 'ProvinceSupervisor', + pageSize: 50, + search: 'filter', + + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + submitInspectionList.value = + Resource>.empty(); + } else { + submitInspectionList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(submitInspectionList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + Future pickImages() async { + determineCurrentPosition(); + var tmp = await pickCameraImage(); + if (tmp?.path != null && pickedImages.length < 7) { + pickedImages.add(tmp!); + } + } + + void removeImage(int index) { + pickedImages.removeAt(index); + } + + void closeBottomSheet() { + Get.back(); + } + + double calculateUploadProgress({required int sent, required int total}) { + if (total != 0) { + double progress = (sent * 100 / total) / 100; + return progress; + } else { + return 0.0; + } + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + void setSearchValue(String? data) { + dLog('Search Value: $data'); + searchedValue.value = data?.trim(); + getReport(); + } + + Future onRefresh() async { + currentPage.value = 1; + await getReport(); + } + + String getStatus(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return 'در حال بررسی'; + } + return status; + } + + Color getStatusColor(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return AppColor.yellowNormal; + } + // می‌توانید منطق رنگ را بر اساس inspectionStatus تنظیم کنید + return AppColor.greenNormal; + } +} diff --git a/packages/chicken/lib/features/province_supervisor/presentation/pages/new_inspection/view.dart b/packages/chicken/lib/features/province_supervisor/presentation/pages/new_inspection/view.dart new file mode 100644 index 0000000..e07d0ba --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/presentation/pages/new_inspection/view.dart @@ -0,0 +1,1134 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class NewInspectionPage extends GetView { + const NewInspectionPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasBack: true, + hasFilter: true, + hasSearch: true, + onFilterTap: () { + Get.bottomSheet(filterBottomSheet()); + }, + onRefresh: controller.onRefresh, + onSearchChanged: (data) => controller.setSearchValue(data), + backId: provinceSupervisorActionKey, + routesWidget: ContainerBreadcrumb(rxRoutes: controller.routesName), + child: Column(children: [reportWidget()]), + ); + } + + Widget reportWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidgetReport(item), + secondChild: itemListExpandedWidgetReport(item), + labelColor: AppColor.greenLight, + labelIcon: Assets.vec.cubeSearchSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getReport(true), + ); + }, controller.submitInspectionList), + ); + } + + Widget itemListExpandedWidgetReport(PoultryScienceReport item) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + spacing: 8, + children: [ + buildRow( + title: 'وضعیت بازرسی', + value: controller.getStatus(item), + titleStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + valueStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + ), + if (item.hatching?.poultry?.unitName != null) + buildRow( + title: 'مرغداری', + value: item.hatching?.poultry?.unitName ?? '-', + ), + if (item.hatching?.id != null) + buildRow( + title: 'شناسه جوجه‌ریزی', + value: item.hatching!.id.toString(), + ), + + if (item + .reportInformation + ?.technicalOfficer + ?.technicalHealthOfficer != + null) + buildRow( + title: 'کارشناس بهداشت', + value: + item + .reportInformation! + .technicalOfficer! + .technicalHealthOfficer ?? + '-', + ), + if (item + .reportInformation + ?.technicalOfficer + ?.technicalEngineeringOfficer != + null) + buildRow( + title: 'کارشناس فنی', + value: + item + .reportInformation! + .technicalOfficer! + .technicalEngineeringOfficer ?? + '-', + ), + if (item.reportInformation?.casualties?.normalLosses != null || + item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات عادی', + value: (item.reportInformation?.casualties?.normalLosses ?? 0) + .toString(), + unit: '(قطعه)', + ), + if (item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات غیرعادی', + value: item.reportInformation!.casualties!.abnormalLosses + .toString(), + unit: '(قطعه)', + ), + + if (item.reportInformation?.inspectionNotes != null && + item.reportInformation!.inspectionNotes!.isNotEmpty) + buildRow( + title: 'یادداشت بازرسی', + value: item.reportInformation!.inspectionNotes ?? '-', + ), + + RElevated( + text: 'جزییات', + isFullWidth: true, + width: 150.w, + height: 40.h, + onPressed: () { + showDetailsBottomSheet(item); + }, + textStyle: AppFonts.yekan16.copyWith(color: Colors.white), + backgroundColor: AppColor.greenNormal, + ), + ], + ), + ); + } + + Row itemListWidgetReport(PoultryScienceReport item) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox(width: 20), + Expanded( + flex: 2, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 5, + children: [ + Text( + 'شناسه جوجه‌ریزی: ${item.hatching?.id ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + + Text( + item.createDate?.toJalali.formatCompactDate() ?? '-', + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + Expanded( + flex: 3, + child: Column( + spacing: 5, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'مرغداری: ${item.hatching?.poultry?.unitName ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + if (item.reportInformation?.inspectionStatus != null) + Text( + 'وضعیت بازرسی: ${item.reportInformation?.inspectionStatus ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + ], + ); + } + + void showDetailsBottomSheet(PoultryScienceReport item) { + Get.bottomSheet( + isScrollControlled: true, + BaseBottomSheet( + height: Get.height * 0.8, + rootChild: DetailsBottomSheetWidget(item: item), + ), + ); + } + + 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.getReport(); + Get.back(); + }, + height: 40, + ), + ], + ), + ); + } +} + +class DetailsBottomSheetWidget extends StatefulWidget { + final PoultryScienceReport item; + + const DetailsBottomSheetWidget({super.key, required this.item}); + + @override + State createState() => + _DetailsBottomSheetWidgetState(); +} + +class _DetailsBottomSheetWidgetState extends State { + int selectedTabIndex = 0; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 10, + ), + child: Text( + 'جزییات', + style: AppFonts.yekan18Bold.copyWith( + color: AppColor.iconColor, + ), + ), + ), + ], + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + tabBarWidget( + [ + 'اطلاعات کلی', + 'شرایط سالن', + 'تلفات', + 'کارشناس', + 'نهاده', + 'زیرساخت', + 'نیروی انسانی', + 'تسهیلات', + ], + selectedTabIndex, + (index) => setState(() => selectedTabIndex = index), + ), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _buildTableContent(), + ), + ), + ), + ], + ); + } + + Widget _buildTableContent() { + switch (selectedTabIndex) { + case 0: + return generalInfoTable(); + case 1: + return generalConditionHallTable(); + case 2: + return casualtiesTable(); + case 3: + return technicalOfficerTable(); + case 4: + return inputStatusTable(); + case 5: + return infrastructureEnergyTable(); + case 6: + return hrTable(); + case 7: + return facilitiesTable(); + default: + return generalInfoTable(); + } + } + + Widget technicalOfficerTable() { + final officer = widget.item.reportInformation?.technicalOfficer; + if (officer == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'کارشناس فنی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'کارشناس بهداشت', + value: officer.technicalHealthOfficer ?? '-', + ), + rTableRow( + title: 'کارشناس فنی', + value: officer.technicalEngineeringOfficer ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget generalInfoTable() { + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'اطلاعات کلی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت بازرسی', + value: + widget.item.reportInformation?.inspectionStatus ?? + widget.item.state ?? + '-', + ), + rTableRow( + title: 'یادداشت بازرسی', + value: widget.item.reportInformation?.inspectionNotes ?? '-', + ), + rTableRow(title: 'نقش', value: widget.item.reporterRole ?? '-'), + if (widget.item.lat != null && widget.item.log != null) + rTableRow( + title: 'موقعیت', + value: '${widget.item.lat}, ${widget.item.log}', + ), + if (widget.item.hatching?.id != null) + rTableRow( + title: 'شناسه جوجه ریزی', + value: widget.item.hatching!.id.toString(), + ), + ], + ), + ), + ], + ); + } + + Widget generalConditionHallTable() { + final hall = widget.item.reportInformation?.generalConditionHall; + if (hall == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'شرایط عمومی سالن', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow(title: 'وضعیت سلامت', value: hall.healthStatus ?? '-'), + rTableRow( + title: 'وضعیت تهویه', + value: hall.ventilationStatus ?? '-', + ), + rTableRow(title: 'وضعیت بستر', value: hall.bedCondition ?? '-'), + rTableRow(title: 'دما', value: hall.temperature ?? '-'), + rTableRow( + title: 'منبع آب آشامیدنی', + value: hall.drinkingWaterSource ?? '-', + ), + rTableRow( + title: 'کیفیت آب آشامیدنی', + value: hall.drinkingWaterQuality ?? '-', + ), + ], + ), + ), + if (hall.images != null && hall.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: hall.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(hall.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(hall.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget casualtiesTable() { + final casualties = widget.item.reportInformation?.casualties; + if (casualties == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تلفات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (casualties.normalLosses != null) + rTableRow( + title: 'تلفات عادی', + value: casualties.normalLosses.toString(), + ), + if (casualties.abnormalLosses != null) + rTableRow( + title: 'تلفات غیرعادی', + value: casualties.abnormalLosses.toString(), + ), + rTableRow( + title: 'منبع جوجه ریزی', + value: casualties.sourceOfHatching ?? '-', + ), + rTableRow( + title: 'علت تلفات غیرعادی', + value: casualties.causeAbnormalLosses ?? '-', + ), + rTableRow( + title: 'نوع بیماری', + value: casualties.typeDisease ?? '-', + ), + rTableRow( + title: 'نمونه‌برداری انجام شده', + value: casualties.samplingDone == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع نمونه‌برداری', + value: casualties.typeSampling ?? '-', + ), + ], + ), + ), + if (casualties.images != null && casualties.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: casualties.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(casualties.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(casualties.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget inputStatusTable() { + final inputStatus = widget.item.reportInformation?.inputStatus; + if (inputStatus == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'وضعیت نهاده', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت نهاده', + value: inputStatus.inputStatus ?? '-', + ), + rTableRow( + title: 'نام شرکت', + value: inputStatus.companyName ?? '-', + ), + rTableRow( + title: 'کد پیگیری', + value: inputStatus.trackingCode ?? '-', + ), + rTableRow( + title: 'نوع دانه', + value: inputStatus.typeOfGrain ?? '-', + ), + rTableRow( + title: 'موجودی در انبار', + value: inputStatus.inventoryInWarehouse ?? '-', + ), + rTableRow( + title: 'موجودی تا بازدید', + value: inputStatus.inventoryUntilVisit ?? '-', + ), + rTableRow( + title: 'درجه دانه', + value: inputStatus.gradeGrain ?? '-', + ), + ], + ), + ), + if (inputStatus.images != null && inputStatus.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: inputStatus.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(inputStatus.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(inputStatus.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget infrastructureEnergyTable() { + final infra = widget.item.reportInformation?.infrastructureEnergy; + if (infra == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'زیرساخت و انرژی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'نوع ژنراتور', + value: infra.generatorType ?? '-', + ), + rTableRow( + title: 'مدل ژنراتور', + value: infra.generatorModel ?? '-', + ), + rTableRow( + title: 'تعداد ژنراتور', + value: infra.generatorCount ?? '-', + ), + rTableRow( + title: 'ظرفیت ژنراتور', + value: infra.generatorCapacity ?? '-', + ), + rTableRow(title: 'نوع سوخت', value: infra.fuelType ?? '-'), + rTableRow( + title: 'عملکرد ژنراتور', + value: infra.generatorPerformance ?? '-', + ), + rTableRow( + title: 'موجودی سوخت اضطراری', + value: infra.emergencyFuelInventory ?? '-', + ), + rTableRow( + title: 'تاریخچه قطع برق', + value: infra.hasPowerCutHistory == true ? 'بله' : 'خیر', + ), + if (infra.hasPowerCutHistory == true) ...[ + rTableRow( + title: 'مدت قطع برق', + value: infra.powerCutDuration ?? '-', + ), + rTableRow( + title: 'ساعت قطع برق', + value: infra.powerCutHour ?? '-', + ), + ], + rTableRow( + title: 'یادداشت اضافی', + value: infra.additionalNotes ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget hrTable() { + final hr = widget.item.reportInformation?.hr; + if (hr == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'نیروی انسانی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (hr.numberEmployed != null) + rTableRow( + title: 'تعداد شاغلین', + value: hr.numberEmployed.toString(), + ), + if (hr.numberIndigenous != null) + rTableRow( + title: 'تعداد بومی', + value: hr.numberIndigenous.toString(), + ), + if (hr.numberNonIndigenous != null) + rTableRow( + title: 'تعداد غیربومی', + value: hr.numberNonIndigenous.toString(), + ), + rTableRow( + title: 'وضعیت قرارداد', + value: hr.contractStatus ?? '-', + ), + rTableRow( + title: 'آموزش دیده', + value: hr.trained == true ? 'بله' : 'خیر', + ), + ], + ), + ), + ], + ); + } + + Widget facilitiesTable() { + final facilities = widget.item.reportInformation?.facilities; + if (facilities == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تسهیلات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'دارای تسهیلات', + value: facilities.hasFacilities == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع تسهیلات', + value: facilities.typeOfFacility ?? '-', + ), + if (facilities.amount != null) + rTableRow(title: 'مبلغ', value: facilities.amount.toString()), + rTableRow(title: 'تاریخ', value: facilities.date ?? '-'), + rTableRow( + title: 'وضعیت بازپرداخت', + value: facilities.repaymentStatus ?? '-', + ), + rTableRow( + title: 'درخواست تسهیلات', + value: facilities.requestFacilities ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget tabBarWidget( + List tabs, + int selectedIndex, + Function(int) onTabSelected, + ) { + return SizedBox( + height: 40.h, + width: Get.width, + child: Column( + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + reverse: true, + child: Row( + children: [ + ...tabs.map( + (tab) => GestureDetector( + onTap: () => onTabSelected(tabs.indexOf(tab)), + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 11, + ), + decoration: BoxDecoration( + border: tab == tabs[selectedIndex] + ? Border( + bottom: BorderSide( + color: AppColor.blueNormalOld, + width: 3, + ), + ) + : null, + ), + child: Text( + tab, + style: AppFonts.yekan12Bold.copyWith( + color: tab == tabs[selectedIndex] + ? AppColor.blueNormalOld + : AppColor.mediumGrey, + ), + ), + ), + ), + ), + ], + ), + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + ], + ), + ); + } + + Row rTableRow({String? title, String? value}) { + return Row( + children: [ + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + color: AppColor.bgLight2, + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + title ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.iconColor), + ), + ), + ), + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + value ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + ), + ), + ], + ); + } + + void showImageDialog(List images, int initialIndex) { + final pageController = PageController(initialPage: initialIndex); + final currentIndex = initialIndex.obs; + + Get.dialog( + Dialog( + backgroundColor: Colors.transparent, + insetPadding: EdgeInsets.zero, + child: Container( + width: Get.width, + height: Get.height, + color: Colors.black, + child: Stack( + children: [ + PageView.builder( + controller: pageController, + itemCount: images.length, + onPageChanged: (index) { + currentIndex.value = index; + }, + itemBuilder: (context, index) { + return InteractiveViewer( + minScale: 0.5, + maxScale: 4.0, + child: Center( + child: Image.network( + images[index], + fit: BoxFit.contain, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + color: Colors.white, + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Center( + child: Icon( + Icons.error, + color: Colors.white, + size: 50, + ), + ); + }, + ), + ), + ); + }, + ), + Positioned( + top: 40, + right: 16, + child: IconButton( + icon: Icon(Icons.close, color: Colors.white, size: 30), + onPressed: () => Get.back(), + ), + ), + if (images.length > 1) + Positioned( + bottom: 20, + left: 0, + right: 0, + child: Center( + child: Obx( + () => Container( + padding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '${currentIndex.value + 1} / ${images.length}', + style: TextStyle(color: Colors.white, fontSize: 14), + ), + ), + ), + ), + ), + ], + ), + ), + ), + barrierDismissible: true, + ); + } +} diff --git a/packages/chicken/lib/features/province_supervisor/presentation/pages/root/logic.dart b/packages/chicken/lib/features/province_supervisor/presentation/pages/root/logic.dart new file mode 100644 index 0000000..642f191 --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/presentation/pages/root/logic.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/data/repositories/province_supervisor_repository.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/presentation/pages/home/view.dart'; +import 'package:rasadyar_chicken/features/common/presentation/page/profile/view.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/presentation/routes/pages.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/utils/utils.dart'; +import 'package:rasadyar_core/core.dart'; + +enum ErrorLocationType { serviceDisabled, permissionDenied, none } + +class ProvinceSupervisorRootLogic extends GetxController { + var tokenService = Get.find(); + + late ProvinceSupervisorRepository provinceSupervisorRepository; + + RxList errorLocationType = RxList(); + RxMap homeExpandedList = RxMap(); + DateTime? _lastBackPressed; + + RxInt currentPage = 0.obs; + + final pages = [ + Navigator( + key: Get.nestedKey(provinceSupervisorActionKey), + onGenerateRoute: (settings) { + final page = ProvinceSupervisorPages.pages.firstWhere( + (e) => e.name == settings.name, + orElse: () => ProvinceSupervisorPages.pages.firstWhere( + (e) => e.name == ProvinceSupervisorRoutes.homeProvinceSupervisor, + ), + ); + + return buildRouteFromGetPage(page); + }, + ), + + ProfilePage(), + ]; + + @override + void onInit() { + super.onInit(); + provinceSupervisorRepository = diChicken + .get(); + } + + void toggleExpanded(int index) { + if (homeExpandedList.keys.contains(index)) { + homeExpandedList.remove(index); + } else { + homeExpandedList[index] = false; + } + } + + void rootErrorHandler(DioException error) { + handleGeneric(error, () { + tokenService.deleteModuleTokens(Module.chicken); + }); + } + + void changePage(int index) { + currentPage.value = index; + } + + void popBackTaped() async { + 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(); + } + } +} diff --git a/packages/chicken/lib/features/province_supervisor/presentation/pages/root/view.dart b/packages/chicken/lib/features/province_supervisor/presentation/pages/root/view.dart new file mode 100644 index 0000000..95b26d0 --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/presentation/pages/root/view.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class ProvinceSupervisorRootPage extends GetView { + const ProvinceSupervisorRootPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isFullScreen: true, + onPopScopTaped: controller.popBackTaped, + child: ObxValue((data) { + return Stack( + children: [ + IndexedStack(children: controller.pages, index: data.value), + Positioned( + right: 0, + left: 0, + bottom: 0, + child: RBottomNavigation( + mainAxisAlignment: MainAxisAlignment.spaceAround, + items: [ + RBottomNavigationItem( + label: 'خانه', + icon: Assets.vec.homeSvg.path, + isSelected: controller.currentPage.value == 0, + onTap: () { + Get.nestedKey( + provinceSupervisorActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(0); + }, + ), + RBottomNavigationItem( + label: 'پروفایل', + icon: Assets.vec.profileCircleSvg.path, + isSelected: controller.currentPage.value == 1, + onTap: () { + Get.nestedKey( + provinceSupervisorActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(1); + }, + ), + ], + ), + ), + ], + ); + }, controller.currentPage), + ); + } +} diff --git a/packages/chicken/lib/features/province_supervisor/presentation/routes/pages.dart b/packages/chicken/lib/features/province_supervisor/presentation/routes/pages.dart new file mode 100644 index 0000000..21c4320 --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/presentation/routes/pages.dart @@ -0,0 +1,64 @@ +import 'package:rasadyar_chicken/features/province_supervisor/presentation/pages/home/logic.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/presentation/pages/home/view.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/presentation/pages/root/logic.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/presentation/pages/root/view.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/presentation/pages/new_inspection/logic.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/presentation/pages/new_inspection/view.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/presentation/pages/active_hatching/view.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/routes/global_binding.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class ProvinceSupervisorPages { + ProvinceSupervisorPages._(); + + static List get pages => [ + GetPage( + name: ProvinceSupervisorRoutes.initProvinceSupervisor, + page: () => ProvinceSupervisorRootPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ChickenBaseLogic(), fenix: true); + Get.lazyPut(() => ProvinceSupervisorRootLogic()); + Get.lazyPut(() => ProvinceSupervisorHomeLogic()); + }), + ], + ), + GetPage( + name: ProvinceSupervisorRoutes.homeProvinceSupervisor, + page: () => ProvinceSupervisorHomePage(), + middlewares: [AuthMiddleware()], + binding: BindingsBuilder(() { + Get.put(ProvinceSupervisorHomeLogic()); + Get.lazyPut(() => ChickenBaseLogic()); + }), + ), + + GetPage( + name: ProvinceSupervisorRoutes.activeHatchingProvinceSupervisor, + page: () => ActiveHatchingPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ActiveHatchingLogic()); + }), + ], + ), + GetPage( + name: ProvinceSupervisorRoutes.newInspectionProvinceSupervisor, + page: () => NewInspectionPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => NewInspectionLogic()); + }), + ], + ), + ]; +} diff --git a/packages/chicken/lib/features/province_supervisor/presentation/routes/routes.dart b/packages/chicken/lib/features/province_supervisor/presentation/routes/routes.dart new file mode 100644 index 0000000..547f355 --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/presentation/routes/routes.dart @@ -0,0 +1,10 @@ +sealed class ProvinceSupervisorRoutes { + ProvinceSupervisorRoutes._(); + + static const _base = '/chicken/provinceSupervisor'; + static const initProvinceSupervisor = '$_base/'; + static const homeProvinceSupervisor = '$_base/home'; + static const actionProvinceSupervisor = '$_base/action'; + static const activeHatchingProvinceSupervisor = '$_base/activeHatching'; + static const newInspectionProvinceSupervisor = '$_base/newInspection'; +} diff --git a/packages/chicken/lib/features/province_supervisor/province_supervisor.dart b/packages/chicken/lib/features/province_supervisor/province_supervisor.dart new file mode 100644 index 0000000..3824e90 --- /dev/null +++ b/packages/chicken/lib/features/province_supervisor/province_supervisor.dart @@ -0,0 +1,3 @@ +export 'data/di/province_supervisor_di.dart'; +export 'presentation/routes/routes.dart'; +export 'presentation/routes/pages.dart'; diff --git a/packages/chicken/lib/features/super_admin/data/datasources/remote/super_admin_remote_data_source.dart b/packages/chicken/lib/features/super_admin/data/datasources/remote/super_admin_remote_data_source.dart new file mode 100644 index 0000000..d4fcff1 --- /dev/null +++ b/packages/chicken/lib/features/super_admin/data/datasources/remote/super_admin_remote_data_source.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class SuperAdminRemoteDataSource { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/super_admin/data/datasources/remote/super_admin_remote_data_source_impl.dart b/packages/chicken/lib/features/super_admin/data/datasources/remote/super_admin_remote_data_source_impl.dart new file mode 100644 index 0000000..633157a --- /dev/null +++ b/packages/chicken/lib/features/super_admin/data/datasources/remote/super_admin_remote_data_source_impl.dart @@ -0,0 +1,39 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/super_admin/data/datasources/remote/super_admin_remote_data_source.dart'; +import 'package:rasadyar_core/core.dart'; + +class SuperAdminRemoteDataSourceImpl implements SuperAdminRemoteDataSource { + final DioRemote _httpClient; + + SuperAdminRemoteDataSourceImpl(this._httpClient); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + var res = await _httpClient.get( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + queryParameters: queryParameters, + fromJson: (json) => PaginationModel.fromJson( + json, + (json) => PoultryScienceReport.fromJson(json as Map), + ), + ); + return res.data; + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + await _httpClient.post( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + data: request.toJson(), + ); + } +} diff --git a/packages/chicken/lib/features/super_admin/data/di/super_admin_di.dart b/packages/chicken/lib/features/super_admin/data/di/super_admin_di.dart new file mode 100644 index 0000000..0dda3e1 --- /dev/null +++ b/packages/chicken/lib/features/super_admin/data/di/super_admin_di.dart @@ -0,0 +1,36 @@ +import 'package:rasadyar_chicken/features/super_admin/data/datasources/remote/super_admin_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/super_admin/data/datasources/remote/super_admin_remote_data_source_impl.dart'; +import 'package:rasadyar_chicken/features/super_admin/data/repositories/super_admin_repository.dart'; +import 'package:rasadyar_chicken/features/super_admin/data/repositories/super_admin_repository_impl.dart'; +import 'package:rasadyar_core/core.dart'; + +/// Setup dependency injection for super_admin feature +Future setupSuperAdminDI(GetIt di, DioRemote dioRemote) async { + di.registerLazySingleton( + () => SuperAdminRemoteDataSourceImpl(dioRemote), + ); + + di.registerLazySingleton( + () => SuperAdminRepositoryImpl(di.get()), + ); +} + +/// Re-register super_admin dependencies (used when base URL changes) +Future reRegisterSuperAdminDI(GetIt di, DioRemote dioRemote) async { + await reRegister(di, () => SuperAdminRemoteDataSourceImpl(dioRemote)); + await reRegister( + di, + () => SuperAdminRepositoryImpl(di.get()), + ); +} + +/// Helper function to re-register a dependency +Future reRegister( + GetIt di, + T Function() factory, +) async { + if (di.isRegistered()) { + await di.unregister(); + } + di.registerLazySingleton(factory); +} diff --git a/packages/chicken/lib/features/super_admin/data/repositories/super_admin_repository.dart b/packages/chicken/lib/features/super_admin/data/repositories/super_admin_repository.dart new file mode 100644 index 0000000..997416a --- /dev/null +++ b/packages/chicken/lib/features/super_admin/data/repositories/super_admin_repository.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class SuperAdminRepository { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/super_admin/data/repositories/super_admin_repository_impl.dart b/packages/chicken/lib/features/super_admin/data/repositories/super_admin_repository_impl.dart new file mode 100644 index 0000000..9b844bf --- /dev/null +++ b/packages/chicken/lib/features/super_admin/data/repositories/super_admin_repository_impl.dart @@ -0,0 +1,30 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/super_admin/data/datasources/remote/super_admin_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/super_admin/data/repositories/super_admin_repository.dart'; +import 'package:rasadyar_core/core.dart'; + +class SuperAdminRepositoryImpl implements SuperAdminRepository { + final SuperAdminRemoteDataSource _remote; + + SuperAdminRepositoryImpl(this._remote); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + return await _remote.getSubmitInspectionList( + token: token, + queryParameters: queryParameters, + ); + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + return await _remote.submitInspection(token: token, request: request); + } +} diff --git a/packages/chicken/lib/features/super_admin/presentation/pages/active_hatching/logic.dart b/packages/chicken/lib/features/super_admin/presentation/pages/active_hatching/logic.dart new file mode 100644 index 0000000..9fbe9b6 --- /dev/null +++ b/packages/chicken/lib/features/super_admin/presentation/pages/active_hatching/logic.dart @@ -0,0 +1,95 @@ +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/repositories/poultry_science_repository.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/pages/root/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingLogic extends GetxController { + SuperAdminRootLogic rootLogic = Get.find(); + BaseLogic baseLogic = Get.find(); + late PoultryScienceRepository poultryScienceRepository; + Rx>> activeHatchingList = + Resource>.loading().obs; + + final RxBool isLoadingMoreList = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + List routesName = ['اقدام', 'جوجه ریزی فعال']; + + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + RxnString searchedValue = RxnString(); + + @override + void onInit() { + super.onInit(); + poultryScienceRepository = diChicken.get(); + } + + @override + void onReady() { + super.onReady(); + getHatchingList(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getHatchingList([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreList.value = true; + } else { + activeHatchingList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => await poultryScienceRepository.getHatchingPoultry( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + queryParams: {'type': 'hatching'}, + role: 'SuperAdmin', + pageSize: 50, + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + activeHatchingList.value = + Resource>.empty(); + } else { + activeHatchingList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(activeHatchingList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + Future onRefresh() async { + currentPage.value = 1; + await getHatchingList(); + } +} diff --git a/packages/chicken/lib/features/super_admin/presentation/pages/active_hatching/view.dart b/packages/chicken/lib/features/super_admin/presentation/pages/active_hatching/view.dart new file mode 100644 index 0000000..c663465 --- /dev/null +++ b/packages/chicken/lib/features/super_admin/presentation/pages/active_hatching/view.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet_logic.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingPage extends GetView { + const ActiveHatchingPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasSearch: true, + hasFilter: false, + backId: superAdminActionKey, + routes: controller.routesName, + onSearchChanged: (data) { + controller.searchedValue.value = data; + controller.getHatchingList(); + }, + child: hatchingWidget(), + /*widgets: [ + hatchingWidget() + ],*/ + ); + } + + Widget hatchingWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidget(item), + secondChild: itemListExpandedWidget(item), + labelColor: AppColor.blueLight, + labelIcon: Assets.vec.activeFramSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getHatchingList(true), + ); + }, controller.activeHatchingList); + } + + Container itemListExpandedWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.center, + style: AppFonts.yekan16.copyWith(color: AppColor.greenDark), + ), + Spacer(), + + Visibility( + child: Text( + item.violation == true ? 'پیگیری' : 'عادی', + textAlign: TextAlign.center, + style: AppFonts.yekan10.copyWith(color: AppColor.redDark), + ), + ), + ], + ), + 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: [ + Text( + 'نژاد:${item.breed?.first.breed ?? 'N/A'}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + Text( + ' سن${item.age} (روز)', + + style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal), + ), + Text( + ' دوره جوجه ریزی:${item.period}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ], + ), + ), + + buildRow( + title: 'شماره مجوز جوجه ریزی', + value: item.licenceNumber ?? 'N/A', + ), + buildUnitRow( + title: 'حجم جوجه ریزی', + value: item.quantity.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'مانده در سالن', + value: item.leftOver.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'تلفات', + value: item.losses.separatedByCommaFa, + unit: '(قطعه)', + ), + buildRow( + title: 'دامپزشک فارم', + value: + '${item.vetFarm?.vetFarmFullName}(${item.vetFarm?.vetFarmMobile})', + ), + buildRow( + title: 'شرح بازرسی', + value: item.reportInfo?.image == false + ? 'ارسال تصویر جوجه ریزی فارم ' + : 'تکمیل شده', + titleStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + valueStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + ), + + RElevated( + height: 40.h, + isFullWidth: true, + onPressed: () { + Get.find().setHatchingModel( + item, + ); + Get.bottomSheet( + CreateInspectionBottomSheet(), + isScrollControlled: true, + ignoreSafeArea: false, + ).then((value) { + if (Get.isRegistered()) { + Get.find().clearForm(); + } + }); + }, + child: Text('ثبت بازرسی'), + ), + ], + ), + ); + } + + Widget itemListWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + Text( + item.poultry?.user?.mobile ?? '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: [ + Text( + item.poultry?.unitName ?? 'N/Aaq', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.bgDark), + ), + Text( + item.poultry?.licenceNumber ?? 'N/A', + textAlign: TextAlign.left, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + ], + ), + ), + Expanded( + flex: 1, + child: Assets.vec.scanSvg.svg( + width: 32.w, + height: 32.h, + colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn), + ), + ), + ], + ); + } +} diff --git a/packages/chicken/lib/features/super_admin/presentation/pages/home/logic.dart b/packages/chicken/lib/features/super_admin/presentation/pages/home/logic.dart new file mode 100644 index 0000000..7a720d3 --- /dev/null +++ b/packages/chicken/lib/features/super_admin/presentation/pages/home/logic.dart @@ -0,0 +1,26 @@ +import 'package:rasadyar_chicken/features/super_admin/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/routes/routes.dart'; +import 'package:rasadyar_core/core.dart'; + +class SuperAdminHomeItem { + final String title; + final String route; + final String icon; + + SuperAdminHomeItem({required this.title, required this.route, required this.icon}); +} + +class SuperAdminHomeLogic extends GetxController { + RxList items = [ + SuperAdminHomeItem( + title: "جوجه ریزی فعال", + route: SuperAdminRoutes.activeHatchingSuperAdmin, + icon: Assets.vec.activeFramSvg.path, + ), + SuperAdminHomeItem( + title: "بازرسی مزارع طیور", + route: SuperAdminRoutes.newInspectionSuperAdmin, + icon: Assets.vec.activeFramSvg.path, + ), + ].obs; +} diff --git a/packages/chicken/lib/features/super_admin/presentation/pages/home/view.dart b/packages/chicken/lib/features/super_admin/presentation/pages/home/view.dart new file mode 100644 index 0000000..9d957ff --- /dev/null +++ b/packages/chicken/lib/features/super_admin/presentation/pages/home/view.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class SuperAdminHomePage extends GetView { + SuperAdminHomePage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isBase: true, + hasNews: true, + hasNotification: true, + child: gridWidget(), + ); + } + + Widget gridWidget() { + return ObxValue((data) { + return GridView.builder( + physics: BouncingScrollPhysics(), + padding: EdgeInsets.symmetric(vertical: 18.h, horizontal: 32.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 24.h, + crossAxisSpacing: 24.w, + ), + itemCount: data.length, + hitTestBehavior: HitTestBehavior.opaque, + itemBuilder: (BuildContext context, int index) { + var item = data[index]; + return GlassMorphismCardIcon( + title: item.title, + vecIcon: item.icon, + onTap: () async { + Get.toNamed(item.route, id: superAdminActionKey); + }, + ); + }, + ); + }, controller.items); + } +} diff --git a/packages/chicken/lib/features/super_admin/presentation/pages/new_inspection/logic.dart b/packages/chicken/lib/features/super_admin/presentation/pages/new_inspection/logic.dart new file mode 100644 index 0000000..d7386fc --- /dev/null +++ b/packages/chicken/lib/features/super_admin/presentation/pages/new_inspection/logic.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/pages/root/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class NewInspectionLogic extends GetxController { + BaseLogic baseLogic = Get.find(); + + Rx>> submitInspectionList = + Resource>.loading().obs; + + SuperAdminRootLogic rootLogic = Get.find(); + + final RxBool isLoadingMoreAllocationsMade = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + RxList pickedImages = [].obs; + final List _multiPartPickedImages = []; + + RxBool isOnUpload = false.obs; + + RxDouble presentUpload = 0.0.obs; + RxList routesName = RxList(); + RxInt selectedSegmentIndex = 0.obs; + + RxnString searchedValue = RxnString(); + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + + @override + void onInit() { + super.onInit(); + + routesName.value = ['اقدام'].toList(); + + ever(selectedSegmentIndex, (callback) { + routesName.removeLast(); + routesName.add(callback == 0 ? 'بازرسی' : 'بایگانی'); + }); + } + + @override + void onReady() { + super.onReady(); + + getReport(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getReport([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreAllocationsMade.value = true; + } else { + submitInspectionList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => + await rootLogic.superAdminRepository.getSubmitInspectionList( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + role: 'SuperAdmin', + pageSize: 50, + search: 'filter', + + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + submitInspectionList.value = + Resource>.empty(); + } else { + submitInspectionList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(submitInspectionList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + Future pickImages() async { + determineCurrentPosition(); + var tmp = await pickCameraImage(); + if (tmp?.path != null && pickedImages.length < 7) { + pickedImages.add(tmp!); + } + } + + void removeImage(int index) { + pickedImages.removeAt(index); + } + + void closeBottomSheet() { + Get.back(); + } + + double calculateUploadProgress({required int sent, required int total}) { + if (total != 0) { + double progress = (sent * 100 / total) / 100; + return progress; + } else { + return 0.0; + } + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + void setSearchValue(String? data) { + dLog('Search Value: $data'); + searchedValue.value = data?.trim(); + getReport(); + } + + Future onRefresh() async { + currentPage.value = 1; + await getReport(); + } + + String getStatus(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return 'در حال بررسی'; + } + return status; + } + + Color getStatusColor(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return AppColor.yellowNormal; + } + // می‌توانید منطق رنگ را بر اساس inspectionStatus تنظیم کنید + return AppColor.greenNormal; + } +} diff --git a/packages/chicken/lib/features/super_admin/presentation/pages/new_inspection/view.dart b/packages/chicken/lib/features/super_admin/presentation/pages/new_inspection/view.dart new file mode 100644 index 0000000..39478b4 --- /dev/null +++ b/packages/chicken/lib/features/super_admin/presentation/pages/new_inspection/view.dart @@ -0,0 +1,1134 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class NewInspectionPage extends GetView { + const NewInspectionPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasBack: true, + hasFilter: true, + hasSearch: true, + onFilterTap: () { + Get.bottomSheet(filterBottomSheet()); + }, + onRefresh: controller.onRefresh, + onSearchChanged: (data) => controller.setSearchValue(data), + backId: superAdminActionKey, + routesWidget: ContainerBreadcrumb(rxRoutes: controller.routesName), + child: Column(children: [reportWidget()]), + ); + } + + Widget reportWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidgetReport(item), + secondChild: itemListExpandedWidgetReport(item), + labelColor: AppColor.greenLight, + labelIcon: Assets.vec.cubeSearchSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getReport(true), + ); + }, controller.submitInspectionList), + ); + } + + Widget itemListExpandedWidgetReport(PoultryScienceReport item) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + spacing: 8, + children: [ + buildRow( + title: 'وضعیت بازرسی', + value: controller.getStatus(item), + titleStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + valueStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + ), + if (item.hatching?.poultry?.unitName != null) + buildRow( + title: 'مرغداری', + value: item.hatching?.poultry?.unitName ?? '-', + ), + if (item.hatching?.id != null) + buildRow( + title: 'شناسه جوجه‌ریزی', + value: item.hatching!.id.toString(), + ), + + if (item + .reportInformation + ?.technicalOfficer + ?.technicalHealthOfficer != + null) + buildRow( + title: 'کارشناس بهداشت', + value: + item + .reportInformation! + .technicalOfficer! + .technicalHealthOfficer ?? + '-', + ), + if (item + .reportInformation + ?.technicalOfficer + ?.technicalEngineeringOfficer != + null) + buildRow( + title: 'کارشناس فنی', + value: + item + .reportInformation! + .technicalOfficer! + .technicalEngineeringOfficer ?? + '-', + ), + if (item.reportInformation?.casualties?.normalLosses != null || + item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات عادی', + value: (item.reportInformation?.casualties?.normalLosses ?? 0) + .toString(), + unit: '(قطعه)', + ), + if (item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات غیرعادی', + value: item.reportInformation!.casualties!.abnormalLosses + .toString(), + unit: '(قطعه)', + ), + + if (item.reportInformation?.inspectionNotes != null && + item.reportInformation!.inspectionNotes!.isNotEmpty) + buildRow( + title: 'یادداشت بازرسی', + value: item.reportInformation!.inspectionNotes ?? '-', + ), + + RElevated( + text: 'جزییات', + isFullWidth: true, + width: 150.w, + height: 40.h, + onPressed: () { + showDetailsBottomSheet(item); + }, + textStyle: AppFonts.yekan16.copyWith(color: Colors.white), + backgroundColor: AppColor.greenNormal, + ), + ], + ), + ); + } + + Row itemListWidgetReport(PoultryScienceReport item) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox(width: 20), + Expanded( + flex: 2, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 5, + children: [ + Text( + 'شناسه جوجه‌ریزی: ${item.hatching?.id ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + + Text( + item.createDate?.toJalali.formatCompactDate() ?? '-', + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + Expanded( + flex: 3, + child: Column( + spacing: 5, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'مرغداری: ${item.hatching?.poultry?.unitName ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + if (item.reportInformation?.inspectionStatus != null) + Text( + 'وضعیت بازرسی: ${item.reportInformation?.inspectionStatus ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + ], + ); + } + + void showDetailsBottomSheet(PoultryScienceReport item) { + Get.bottomSheet( + isScrollControlled: true, + BaseBottomSheet( + height: Get.height * 0.8, + rootChild: DetailsBottomSheetWidget(item: item), + ), + ); + } + + 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.getReport(); + Get.back(); + }, + height: 40, + ), + ], + ), + ); + } +} + +class DetailsBottomSheetWidget extends StatefulWidget { + final PoultryScienceReport item; + + const DetailsBottomSheetWidget({super.key, required this.item}); + + @override + State createState() => + _DetailsBottomSheetWidgetState(); +} + +class _DetailsBottomSheetWidgetState extends State { + int selectedTabIndex = 0; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 10, + ), + child: Text( + 'جزییات', + style: AppFonts.yekan18Bold.copyWith( + color: AppColor.iconColor, + ), + ), + ), + ], + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + tabBarWidget( + [ + 'اطلاعات کلی', + 'شرایط سالن', + 'تلفات', + 'کارشناس', + 'نهاده', + 'زیرساخت', + 'نیروی انسانی', + 'تسهیلات', + ], + selectedTabIndex, + (index) => setState(() => selectedTabIndex = index), + ), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _buildTableContent(), + ), + ), + ), + ], + ); + } + + Widget _buildTableContent() { + switch (selectedTabIndex) { + case 0: + return generalInfoTable(); + case 1: + return generalConditionHallTable(); + case 2: + return casualtiesTable(); + case 3: + return technicalOfficerTable(); + case 4: + return inputStatusTable(); + case 5: + return infrastructureEnergyTable(); + case 6: + return hrTable(); + case 7: + return facilitiesTable(); + default: + return generalInfoTable(); + } + } + + Widget technicalOfficerTable() { + final officer = widget.item.reportInformation?.technicalOfficer; + if (officer == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'کارشناس فنی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'کارشناس بهداشت', + value: officer.technicalHealthOfficer ?? '-', + ), + rTableRow( + title: 'کارشناس فنی', + value: officer.technicalEngineeringOfficer ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget generalInfoTable() { + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'اطلاعات کلی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت بازرسی', + value: + widget.item.reportInformation?.inspectionStatus ?? + widget.item.state ?? + '-', + ), + rTableRow( + title: 'یادداشت بازرسی', + value: widget.item.reportInformation?.inspectionNotes ?? '-', + ), + rTableRow(title: 'نقش', value: widget.item.reporterRole ?? '-'), + if (widget.item.lat != null && widget.item.log != null) + rTableRow( + title: 'موقعیت', + value: '${widget.item.lat}, ${widget.item.log}', + ), + if (widget.item.hatching?.id != null) + rTableRow( + title: 'شناسه جوجه ریزی', + value: widget.item.hatching!.id.toString(), + ), + ], + ), + ), + ], + ); + } + + Widget generalConditionHallTable() { + final hall = widget.item.reportInformation?.generalConditionHall; + if (hall == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'شرایط عمومی سالن', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow(title: 'وضعیت سلامت', value: hall.healthStatus ?? '-'), + rTableRow( + title: 'وضعیت تهویه', + value: hall.ventilationStatus ?? '-', + ), + rTableRow(title: 'وضعیت بستر', value: hall.bedCondition ?? '-'), + rTableRow(title: 'دما', value: hall.temperature ?? '-'), + rTableRow( + title: 'منبع آب آشامیدنی', + value: hall.drinkingWaterSource ?? '-', + ), + rTableRow( + title: 'کیفیت آب آشامیدنی', + value: hall.drinkingWaterQuality ?? '-', + ), + ], + ), + ), + if (hall.images != null && hall.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: hall.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(hall.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(hall.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget casualtiesTable() { + final casualties = widget.item.reportInformation?.casualties; + if (casualties == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تلفات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (casualties.normalLosses != null) + rTableRow( + title: 'تلفات عادی', + value: casualties.normalLosses.toString(), + ), + if (casualties.abnormalLosses != null) + rTableRow( + title: 'تلفات غیرعادی', + value: casualties.abnormalLosses.toString(), + ), + rTableRow( + title: 'منبع جوجه ریزی', + value: casualties.sourceOfHatching ?? '-', + ), + rTableRow( + title: 'علت تلفات غیرعادی', + value: casualties.causeAbnormalLosses ?? '-', + ), + rTableRow( + title: 'نوع بیماری', + value: casualties.typeDisease ?? '-', + ), + rTableRow( + title: 'نمونه‌برداری انجام شده', + value: casualties.samplingDone == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع نمونه‌برداری', + value: casualties.typeSampling ?? '-', + ), + ], + ), + ), + if (casualties.images != null && casualties.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: casualties.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(casualties.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(casualties.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget inputStatusTable() { + final inputStatus = widget.item.reportInformation?.inputStatus; + if (inputStatus == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'وضعیت نهاده', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت نهاده', + value: inputStatus.inputStatus ?? '-', + ), + rTableRow( + title: 'نام شرکت', + value: inputStatus.companyName ?? '-', + ), + rTableRow( + title: 'کد پیگیری', + value: inputStatus.trackingCode ?? '-', + ), + rTableRow( + title: 'نوع دانه', + value: inputStatus.typeOfGrain ?? '-', + ), + rTableRow( + title: 'موجودی در انبار', + value: inputStatus.inventoryInWarehouse ?? '-', + ), + rTableRow( + title: 'موجودی تا بازدید', + value: inputStatus.inventoryUntilVisit ?? '-', + ), + rTableRow( + title: 'درجه دانه', + value: inputStatus.gradeGrain ?? '-', + ), + ], + ), + ), + if (inputStatus.images != null && inputStatus.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: inputStatus.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(inputStatus.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(inputStatus.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget infrastructureEnergyTable() { + final infra = widget.item.reportInformation?.infrastructureEnergy; + if (infra == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'زیرساخت و انرژی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'نوع ژنراتور', + value: infra.generatorType ?? '-', + ), + rTableRow( + title: 'مدل ژنراتور', + value: infra.generatorModel ?? '-', + ), + rTableRow( + title: 'تعداد ژنراتور', + value: infra.generatorCount ?? '-', + ), + rTableRow( + title: 'ظرفیت ژنراتور', + value: infra.generatorCapacity ?? '-', + ), + rTableRow(title: 'نوع سوخت', value: infra.fuelType ?? '-'), + rTableRow( + title: 'عملکرد ژنراتور', + value: infra.generatorPerformance ?? '-', + ), + rTableRow( + title: 'موجودی سوخت اضطراری', + value: infra.emergencyFuelInventory ?? '-', + ), + rTableRow( + title: 'تاریخچه قطع برق', + value: infra.hasPowerCutHistory == true ? 'بله' : 'خیر', + ), + if (infra.hasPowerCutHistory == true) ...[ + rTableRow( + title: 'مدت قطع برق', + value: infra.powerCutDuration ?? '-', + ), + rTableRow( + title: 'ساعت قطع برق', + value: infra.powerCutHour ?? '-', + ), + ], + rTableRow( + title: 'یادداشت اضافی', + value: infra.additionalNotes ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget hrTable() { + final hr = widget.item.reportInformation?.hr; + if (hr == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'نیروی انسانی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (hr.numberEmployed != null) + rTableRow( + title: 'تعداد شاغلین', + value: hr.numberEmployed.toString(), + ), + if (hr.numberIndigenous != null) + rTableRow( + title: 'تعداد بومی', + value: hr.numberIndigenous.toString(), + ), + if (hr.numberNonIndigenous != null) + rTableRow( + title: 'تعداد غیربومی', + value: hr.numberNonIndigenous.toString(), + ), + rTableRow( + title: 'وضعیت قرارداد', + value: hr.contractStatus ?? '-', + ), + rTableRow( + title: 'آموزش دیده', + value: hr.trained == true ? 'بله' : 'خیر', + ), + ], + ), + ), + ], + ); + } + + Widget facilitiesTable() { + final facilities = widget.item.reportInformation?.facilities; + if (facilities == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تسهیلات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'دارای تسهیلات', + value: facilities.hasFacilities == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع تسهیلات', + value: facilities.typeOfFacility ?? '-', + ), + if (facilities.amount != null) + rTableRow(title: 'مبلغ', value: facilities.amount.toString()), + rTableRow(title: 'تاریخ', value: facilities.date ?? '-'), + rTableRow( + title: 'وضعیت بازپرداخت', + value: facilities.repaymentStatus ?? '-', + ), + rTableRow( + title: 'درخواست تسهیلات', + value: facilities.requestFacilities ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget tabBarWidget( + List tabs, + int selectedIndex, + Function(int) onTabSelected, + ) { + return SizedBox( + height: 40.h, + width: Get.width, + child: Column( + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + reverse: true, + child: Row( + children: [ + ...tabs.map( + (tab) => GestureDetector( + onTap: () => onTabSelected(tabs.indexOf(tab)), + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 11, + ), + decoration: BoxDecoration( + border: tab == tabs[selectedIndex] + ? Border( + bottom: BorderSide( + color: AppColor.blueNormalOld, + width: 3, + ), + ) + : null, + ), + child: Text( + tab, + style: AppFonts.yekan12Bold.copyWith( + color: tab == tabs[selectedIndex] + ? AppColor.blueNormalOld + : AppColor.mediumGrey, + ), + ), + ), + ), + ), + ], + ), + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + ], + ), + ); + } + + Row rTableRow({String? title, String? value}) { + return Row( + children: [ + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + color: AppColor.bgLight2, + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + title ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.iconColor), + ), + ), + ), + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + value ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + ), + ), + ], + ); + } + + void showImageDialog(List images, int initialIndex) { + final pageController = PageController(initialPage: initialIndex); + final currentIndex = initialIndex.obs; + + Get.dialog( + Dialog( + backgroundColor: Colors.transparent, + insetPadding: EdgeInsets.zero, + child: Container( + width: Get.width, + height: Get.height, + color: Colors.black, + child: Stack( + children: [ + PageView.builder( + controller: pageController, + itemCount: images.length, + onPageChanged: (index) { + currentIndex.value = index; + }, + itemBuilder: (context, index) { + return InteractiveViewer( + minScale: 0.5, + maxScale: 4.0, + child: Center( + child: Image.network( + images[index], + fit: BoxFit.contain, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + color: Colors.white, + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Center( + child: Icon( + Icons.error, + color: Colors.white, + size: 50, + ), + ); + }, + ), + ), + ); + }, + ), + Positioned( + top: 40, + right: 16, + child: IconButton( + icon: Icon(Icons.close, color: Colors.white, size: 30), + onPressed: () => Get.back(), + ), + ), + if (images.length > 1) + Positioned( + bottom: 20, + left: 0, + right: 0, + child: Center( + child: Obx( + () => Container( + padding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '${currentIndex.value + 1} / ${images.length}', + style: TextStyle(color: Colors.white, fontSize: 14), + ), + ), + ), + ), + ), + ], + ), + ), + ), + barrierDismissible: true, + ); + } +} diff --git a/packages/chicken/lib/features/super_admin/presentation/pages/root/logic.dart b/packages/chicken/lib/features/super_admin/presentation/pages/root/logic.dart new file mode 100644 index 0000000..cec976c --- /dev/null +++ b/packages/chicken/lib/features/super_admin/presentation/pages/root/logic.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/super_admin/data/repositories/super_admin_repository.dart'; + +import 'package:rasadyar_chicken/features/common/presentation/page/profile/view.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/routes/pages.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/utils/utils.dart'; +import 'package:rasadyar_core/core.dart'; + +enum ErrorLocationType { serviceDisabled, permissionDenied, none } + +class SuperAdminRootLogic extends GetxController { + var tokenService = Get.find(); + + late SuperAdminRepository superAdminRepository; + + RxList errorLocationType = RxList(); + RxMap homeExpandedList = RxMap(); + DateTime? _lastBackPressed; + + RxInt currentPage = 0.obs; + + final pages = [ + Navigator( + key: Get.nestedKey(superAdminActionKey), + onGenerateRoute: (settings) { + final page = SuperAdminPages.pages.firstWhere( + (e) => e.name == settings.name, + orElse: () => SuperAdminPages.pages.firstWhere( + (e) => e.name == SuperAdminRoutes.homeSuperAdmin, + ), + ); + + return buildRouteFromGetPage(page); + }, + ), + + ProfilePage(), + ]; + + @override + void onInit() { + super.onInit(); + superAdminRepository = diChicken.get(); + } + + void toggleExpanded(int index) { + if (homeExpandedList.keys.contains(index)) { + homeExpandedList.remove(index); + } else { + homeExpandedList[index] = false; + } + } + + void rootErrorHandler(DioException error) { + handleGeneric(error, () { + tokenService.deleteModuleTokens(Module.chicken); + }); + } + + void changePage(int index) { + currentPage.value = index; + } + + void popBackTaped() async { + 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(); + } + } +} diff --git a/packages/chicken/lib/features/super_admin/presentation/pages/root/view.dart b/packages/chicken/lib/features/super_admin/presentation/pages/root/view.dart new file mode 100644 index 0000000..a86b0f1 --- /dev/null +++ b/packages/chicken/lib/features/super_admin/presentation/pages/root/view.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class SuperAdminRootPage extends GetView { + const SuperAdminRootPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isFullScreen: true, + onPopScopTaped: controller.popBackTaped, + child: ObxValue((data) { + return Stack( + children: [ + IndexedStack(children: controller.pages, index: data.value), + Positioned( + right: 0, + left: 0, + bottom: 0, + child: RBottomNavigation( + mainAxisAlignment: MainAxisAlignment.spaceAround, + items: [ + + RBottomNavigationItem( + label: 'خانه', + icon: Assets.vec.homeSvg.path, + isSelected: controller.currentPage.value == 0, + onTap: () { + Get.nestedKey( + superAdminActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(0); + }, + ), + RBottomNavigationItem( + label: 'پروفایل', + icon: Assets.vec.profileCircleSvg.path, + isSelected: controller.currentPage.value == 1, + onTap: () { + Get.nestedKey( + superAdminActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(1); + }, + ), + ], + ), + ), + ], + ); + }, controller.currentPage), + ); + } +} diff --git a/packages/chicken/lib/features/super_admin/presentation/routes/pages.dart b/packages/chicken/lib/features/super_admin/presentation/routes/pages.dart new file mode 100644 index 0000000..d78ad92 --- /dev/null +++ b/packages/chicken/lib/features/super_admin/presentation/routes/pages.dart @@ -0,0 +1,74 @@ +import 'package:rasadyar_chicken/features/super_admin/presentation/pages/home/logic.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/pages/home/view.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/pages/root/logic.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/pages/root/view.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/pages/active_hatching/view.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/pages/new_inspection/logic.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/pages/new_inspection/view.dart'; +import 'package:rasadyar_chicken/features/super_admin/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/routes/global_binding.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class SuperAdminPages { + SuperAdminPages._(); + + static List get pages => [ + GetPage( + name: SuperAdminRoutes.initSuperAdmin, + page: () => SuperAdminRootPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ChickenBaseLogic(), fenix: true); + Get.lazyPut(() => SuperAdminRootLogic()); + Get.lazyPut(() => SuperAdminHomeLogic()); + }), + ], + ), + GetPage( + name: SuperAdminRoutes.homeSuperAdmin, + page: () => SuperAdminHomePage(), + middlewares: [AuthMiddleware()], + binding: BindingsBuilder(() { + Get.put(SuperAdminHomeLogic()); + Get.lazyPut(() => ChickenBaseLogic()); + }), + ), + GetPage( + name: SuperAdminRoutes.actionSuperAdmin, + page: () => SuperAdminHomePage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => SuperAdminHomeLogic()); + }), + ], + ), + GetPage( + name: SuperAdminRoutes.activeHatchingSuperAdmin, + page: () => ActiveHatchingPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ActiveHatchingLogic()); + }), + ], + ), + GetPage( + name: SuperAdminRoutes.newInspectionSuperAdmin, + page: () => NewInspectionPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => NewInspectionLogic()); + }), + ], + ), + ]; +} diff --git a/packages/chicken/lib/features/super_admin/presentation/routes/routes.dart b/packages/chicken/lib/features/super_admin/presentation/routes/routes.dart new file mode 100644 index 0000000..cb2e030 --- /dev/null +++ b/packages/chicken/lib/features/super_admin/presentation/routes/routes.dart @@ -0,0 +1,10 @@ +sealed class SuperAdminRoutes { + SuperAdminRoutes._(); + + static const _base = '/chicken/superAdmin'; + static const initSuperAdmin = '$_base/'; + static const homeSuperAdmin = '$_base/home'; + static const actionSuperAdmin = '$_base/action'; + static const activeHatchingSuperAdmin = '$_base/activeHatching'; + static const newInspectionSuperAdmin = '$_base/newInspection'; +} diff --git a/packages/chicken/lib/features/super_admin/super_admin.dart b/packages/chicken/lib/features/super_admin/super_admin.dart new file mode 100644 index 0000000..2c0cf7f --- /dev/null +++ b/packages/chicken/lib/features/super_admin/super_admin.dart @@ -0,0 +1,3 @@ +export 'data/di/super_admin_di.dart'; +export 'presentation/routes/routes.dart'; +export 'presentation/routes/pages.dart'; diff --git a/packages/chicken/lib/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source.dart b/packages/chicken/lib/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source.dart new file mode 100644 index 0000000..19da44e --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class VetFarmRemoteDataSource { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source_impl.dart b/packages/chicken/lib/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source_impl.dart new file mode 100644 index 0000000..038718d --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source_impl.dart @@ -0,0 +1,39 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source.dart'; +import 'package:rasadyar_core/core.dart'; + +class VetFarmRemoteDataSourceImpl implements VetFarmRemoteDataSource { + final DioRemote _httpClient; + + VetFarmRemoteDataSourceImpl(this._httpClient); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + var res = await _httpClient.get( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + queryParameters: queryParameters, + fromJson: (json) => PaginationModel.fromJson( + json, + (json) => PoultryScienceReport.fromJson(json as Map), + ), + ); + return res.data; + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + await _httpClient.post( + '/poultry_science_report/', + headers: {'Authorization': 'Bearer $token'}, + data: request.toJson(), + ); + } +} diff --git a/packages/chicken/lib/features/vet_farm/data/di/vet_farm_di.dart b/packages/chicken/lib/features/vet_farm/data/di/vet_farm_di.dart new file mode 100644 index 0000000..81c5239 --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/data/di/vet_farm_di.dart @@ -0,0 +1,36 @@ +import 'package:rasadyar_chicken/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source_impl.dart'; +import 'package:rasadyar_chicken/features/vet_farm/data/repositories/vet_farm_repository.dart'; +import 'package:rasadyar_chicken/features/vet_farm/data/repositories/vet_farm_repository_impl.dart'; +import 'package:rasadyar_core/core.dart'; + +/// Setup dependency injection for vet_farm feature +Future setupVetFarmDI(GetIt di, DioRemote dioRemote) async { + di.registerLazySingleton( + () => VetFarmRemoteDataSourceImpl(dioRemote), + ); + + di.registerLazySingleton( + () => VetFarmRepositoryImpl(di.get()), + ); +} + +/// Re-register vet_farm dependencies (used when base URL changes) +Future reRegisterVetFarmDI(GetIt di, DioRemote dioRemote) async { + await reRegister(di, () => VetFarmRemoteDataSourceImpl(dioRemote)); + await reRegister( + di, + () => VetFarmRepositoryImpl(di.get()), + ); +} + +/// Helper function to re-register a dependency +Future reRegister( + GetIt di, + T Function() factory, +) async { + if (di.isRegistered()) { + await di.unregister(); + } + di.registerLazySingleton(factory); +} diff --git a/packages/chicken/lib/features/vet_farm/data/repositories/vet_farm_repository.dart b/packages/chicken/lib/features/vet_farm/data/repositories/vet_farm_repository.dart new file mode 100644 index 0000000..5d55bb7 --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/data/repositories/vet_farm_repository.dart @@ -0,0 +1,15 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_core/core.dart'; + +abstract class VetFarmRepository { + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }); + + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }); +} diff --git a/packages/chicken/lib/features/vet_farm/data/repositories/vet_farm_repository_impl.dart b/packages/chicken/lib/features/vet_farm/data/repositories/vet_farm_repository_impl.dart new file mode 100644 index 0000000..cfed199 --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/data/repositories/vet_farm_repository_impl.dart @@ -0,0 +1,30 @@ +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; +import 'package:rasadyar_chicken/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source.dart'; +import 'package:rasadyar_chicken/features/vet_farm/data/repositories/vet_farm_repository.dart'; +import 'package:rasadyar_core/core.dart'; + +class VetFarmRepositoryImpl implements VetFarmRepository { + final VetFarmRemoteDataSource _remote; + + VetFarmRepositoryImpl(this._remote); + + @override + Future?> getSubmitInspectionList({ + required String token, + Map? queryParameters, + }) async { + return await _remote.getSubmitInspectionList( + token: token, + queryParameters: queryParameters, + ); + } + + @override + Future submitInspection({ + required String token, + required SubmitInspectionResponse request, + }) async { + return await _remote.submitInspection(token: token, request: request); + } +} diff --git a/packages/chicken/lib/features/vet_farm/presentation/pages/active_hatching/logic.dart b/packages/chicken/lib/features/vet_farm/presentation/pages/active_hatching/logic.dart new file mode 100644 index 0000000..e81ebab --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/presentation/pages/active_hatching/logic.dart @@ -0,0 +1,95 @@ +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/repositories/poultry_science_repository.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/root/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingLogic extends GetxController { + VetFarmRootLogic rootLogic = Get.find(); + BaseLogic baseLogic = Get.find(); + late PoultryScienceRepository poultryScienceRepository; + Rx>> activeHatchingList = + Resource>.loading().obs; + + final RxBool isLoadingMoreList = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + List routesName = ['اقدام', 'جوجه ریزی فعال']; + + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + RxnString searchedValue = RxnString(); + + @override + void onInit() { + super.onInit(); + poultryScienceRepository = diChicken.get(); + } + + @override + void onReady() { + super.onReady(); + getHatchingList(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getHatchingList([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreList.value = true; + } else { + activeHatchingList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => await poultryScienceRepository.getHatchingPoultry( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + queryParams: {'type': 'hatching'}, + role: 'VetFarm', + pageSize: 50, + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + activeHatchingList.value = + Resource>.empty(); + } else { + activeHatchingList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(activeHatchingList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + Future onRefresh() async { + currentPage.value = 1; + await getHatchingList(); + } +} diff --git a/packages/chicken/lib/features/vet_farm/presentation/pages/active_hatching/view.dart b/packages/chicken/lib/features/vet_farm/presentation/pages/active_hatching/view.dart new file mode 100644 index 0000000..3d0f986 --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/presentation/pages/active_hatching/view.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/hatching/hatching_models.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet.dart'; +import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/create_inspection_bottom_sheet_logic.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +class ActiveHatchingPage extends GetView { + const ActiveHatchingPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasSearch: true, + hasFilter: false, + backId: vetFarmActionKey, + routes: controller.routesName, + onSearchChanged: (data) { + controller.searchedValue.value = data; + controller.getHatchingList(); + }, + child: hatchingWidget(), + /*widgets: [ + hatchingWidget() + ],*/ + ); + } + + Widget hatchingWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidget(item), + secondChild: itemListExpandedWidget(item), + labelColor: AppColor.blueLight, + labelIcon: Assets.vec.activeFramSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getHatchingList(true), + ); + }, controller.activeHatchingList); + } + + Container itemListExpandedWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.center, + style: AppFonts.yekan16.copyWith(color: AppColor.greenDark), + ), + Spacer(), + + Visibility( + child: Text( + item.violation == true ? 'پیگیری' : 'عادی', + textAlign: TextAlign.center, + style: AppFonts.yekan10.copyWith(color: AppColor.redDark), + ), + ), + ], + ), + 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: [ + Text( + 'نژاد:${item.breed?.first.breed ?? 'N/A'}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + Text( + ' سن${item.age} (روز)', + + style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal), + ), + Text( + ' دوره جوجه ریزی:${item.period}', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ], + ), + ), + + buildRow( + title: 'شماره مجوز جوجه ریزی', + value: item.licenceNumber ?? 'N/A', + ), + buildUnitRow( + title: 'حجم جوجه ریزی', + value: item.quantity.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'مانده در سالن', + value: item.leftOver.separatedByCommaFa, + unit: '(قطعه)', + ), + buildUnitRow( + title: 'تلفات', + value: item.losses.separatedByCommaFa, + unit: '(قطعه)', + ), + buildRow( + title: 'دامپزشک فارم', + value: + '${item.vetFarm?.vetFarmFullName}(${item.vetFarm?.vetFarmMobile})', + ), + buildRow( + title: 'شرح بازرسی', + value: item.reportInfo?.image == false + ? 'ارسال تصویر جوجه ریزی فارم ' + : 'تکمیل شده', + titleStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + valueStyle: AppFonts.yekan14.copyWith( + color: (item.reportInfo?.image ?? false) + ? AppColor.greenNormal + : AppColor.redDark, + ), + ), + + RElevated( + height: 40.h, + isFullWidth: true, + onPressed: () { + Get.find().setHatchingModel( + item, + ); + Get.bottomSheet( + CreateInspectionBottomSheet(), + isScrollControlled: true, + ignoreSafeArea: false, + ).then((value) { + if (Get.isRegistered()) { + Get.find().clearForm(); + } + }); + }, + child: Text('ثبت بازرسی'), + ), + ], + ), + ); + } + + Widget itemListWidget(HatchingModel 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.poultry?.user?.fullname ?? 'N/A', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + Text( + item.poultry?.user?.mobile ?? '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: [ + Text( + item.poultry?.unitName ?? 'N/Aaq', + textAlign: TextAlign.start, + style: AppFonts.yekan12.copyWith(color: AppColor.bgDark), + ), + Text( + item.poultry?.licenceNumber ?? 'N/A', + textAlign: TextAlign.left, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + ], + ), + ), + Expanded( + flex: 1, + child: Assets.vec.scanSvg.svg( + width: 32.w, + height: 32.h, + colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn), + ), + ), + ], + ); + } +} diff --git a/packages/chicken/lib/features/vet_farm/presentation/pages/home/logic.dart b/packages/chicken/lib/features/vet_farm/presentation/pages/home/logic.dart new file mode 100644 index 0000000..25da47a --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/presentation/pages/home/logic.dart @@ -0,0 +1,25 @@ +import 'package:rasadyar_chicken/features/vet_farm/presentation/routes/routes.dart'; +import 'package:rasadyar_core/core.dart'; + +class VetFarmHomeItem { + final String title; + final String route; + final String icon; + + VetFarmHomeItem({required this.title, required this.route, required this.icon}); +} + +class VetFarmHomeLogic extends GetxController { + RxList items = [ + VetFarmHomeItem( + title: "جوجه ریزی فعال", + route: VetFarmRoutes.activeHatchingVetFarm, + icon: Assets.vec.activeFramSvg.path, + ), + VetFarmHomeItem( + title: "بازرسی مزارع طیور", + route: VetFarmRoutes.newInspectionVetFarm, + icon: Assets.vec.activeFramSvg.path, + ), + ].obs; +} diff --git a/packages/chicken/lib/features/vet_farm/presentation/pages/home/view.dart b/packages/chicken/lib/features/vet_farm/presentation/pages/home/view.dart new file mode 100644 index 0000000..c88b6d8 --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/presentation/pages/home/view.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class HomePage extends GetView { + HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isBase: true, + hasNews: true, + hasNotification: true, + child: gridWidget(), + ); + } + + Widget gridWidget() { + return ObxValue((data) { + return GridView.builder( + physics: BouncingScrollPhysics(), + padding: EdgeInsets.symmetric(vertical: 18.h, horizontal: 32.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 24.h, + crossAxisSpacing: 24.w, + ), + itemCount: data.length, + hitTestBehavior: HitTestBehavior.opaque, + itemBuilder: (BuildContext context, int index) { + var item = data[index]; + return GlassMorphismCardIcon( + title: item.title, + vecIcon: item.icon, + onTap: () async { + Get.toNamed(item.route, id: vetFarmActionKey); + }, + ); + }, + ); + }, controller.items); + } +} diff --git a/packages/chicken/lib/features/vet_farm/presentation/pages/new_inspection/logic.dart b/packages/chicken/lib/features/vet_farm/presentation/pages/new_inspection/logic.dart new file mode 100644 index 0000000..931d649 --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/presentation/pages/new_inspection/logic.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/root/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class NewInspectionLogic extends GetxController { + BaseLogic baseLogic = Get.find(); + + Rx>> submitInspectionList = + Resource>.loading().obs; + + VetFarmRootLogic rootLogic = Get.find(); + + final RxBool isLoadingMoreAllocationsMade = false.obs; + RxInt currentPage = 1.obs; + RxInt expandedIndex = RxInt(-1); + RxList pickedImages = [].obs; + final List _multiPartPickedImages = []; + + RxBool isOnUpload = false.obs; + + RxDouble presentUpload = 0.0.obs; + RxList routesName = RxList(); + RxInt selectedSegmentIndex = 0.obs; + + RxnString searchedValue = RxnString(); + Rx fromDateFilter = Jalali.now().obs; + Rx toDateFilter = Jalali.now().obs; + + @override + void onInit() { + super.onInit(); + + routesName.value = ['اقدام'].toList(); + + ever(selectedSegmentIndex, (callback) { + routesName.removeLast(); + routesName.add(callback == 0 ? 'بازرسی' : 'بایگانی'); + }); + } + + @override + void onReady() { + super.onReady(); + + getReport(); + } + + @override + void onClose() { + super.onClose(); + baseLogic.clearSearch(); + } + + Future getReport([bool isLoadingMore = false]) async { + if (isLoadingMore) { + isLoadingMoreAllocationsMade.value = true; + } else { + submitInspectionList.value = + Resource>.loading(); + } + + if (searchedValue.value != null && + searchedValue.value!.trim().isNotEmpty && + currentPage.value > 1) { + currentPage.value = 1; + } + + safeCall( + call: () async => + await rootLogic.vetFarmRepository.getSubmitInspectionList( + token: rootLogic.tokenService.accessToken.value!, + queryParameters: buildQueryParams( + role: 'VetFarm', + pageSize: 50, + search: 'filter', + + page: currentPage.value, + ), + ), + onSuccess: (res) { + if ((res?.count ?? 0) == 0) { + submitInspectionList.value = + Resource>.empty(); + } else { + submitInspectionList.value = + Resource>.success( + PaginationModel( + count: res?.count ?? 0, + next: res?.next, + previous: res?.previous, + results: [ + ...(submitInspectionList.value.data?.results ?? []), + ...(res?.results ?? []), + ], + ), + ); + } + }, + ); + } + + Future pickImages() async { + determineCurrentPosition(); + var tmp = await pickCameraImage(); + if (tmp?.path != null && pickedImages.length < 7) { + pickedImages.add(tmp!); + } + } + + void removeImage(int index) { + pickedImages.removeAt(index); + } + + void closeBottomSheet() { + Get.back(); + } + + double calculateUploadProgress({required int sent, required int total}) { + if (total != 0) { + double progress = (sent * 100 / total) / 100; + return progress; + } else { + return 0.0; + } + } + + void toggleExpanded(int index) { + expandedIndex.value = expandedIndex.value == index ? -1 : index; + } + + void setSearchValue(String? data) { + dLog('Search Value: $data'); + searchedValue.value = data?.trim(); + getReport(); + } + + Future onRefresh() async { + currentPage.value = 1; + await getReport(); + } + + String getStatus(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return 'در حال بررسی'; + } + return status; + } + + Color getStatusColor(PoultryScienceReport item) { + final status = item.reportInformation?.inspectionStatus ?? item.state; + if (status == null || status.isEmpty) { + return AppColor.yellowNormal; + } + // می‌توانید منطق رنگ را بر اساس inspectionStatus تنظیم کنید + return AppColor.greenNormal; + } +} diff --git a/packages/chicken/lib/features/vet_farm/presentation/pages/new_inspection/view.dart b/packages/chicken/lib/features/vet_farm/presentation/pages/new_inspection/view.dart new file mode 100644 index 0000000..e9daf41 --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/presentation/pages/new_inspection/view.dart @@ -0,0 +1,1134 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class NewInspectionPage extends GetView { + const NewInspectionPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + hasBack: true, + hasFilter: true, + hasSearch: true, + onFilterTap: () { + Get.bottomSheet(filterBottomSheet()); + }, + onRefresh: controller.onRefresh, + onSearchChanged: (data) => controller.setSearchValue(data), + backId: vetFarmActionKey, + routesWidget: ContainerBreadcrumb(rxRoutes: controller.routesName), + child: Column(children: [reportWidget()]), + ); + } + + Widget reportWidget() { + return 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.value.isEqual(index), + onTap: () => controller.toggleExpanded(index), + index: index, + child: itemListWidgetReport(item), + secondChild: itemListExpandedWidgetReport(item), + labelColor: AppColor.greenLight, + labelIcon: Assets.vec.cubeSearchSvg.path, + ); + }, controller.expandedIndex); + }, + itemCount: data.value.data?.results?.length ?? 0, + separatorBuilder: (context, index) => SizedBox(height: 8.h), + onLoadMore: () async => controller.getReport(true), + ); + }, controller.submitInspectionList), + ); + } + + Widget itemListExpandedWidgetReport(PoultryScienceReport item) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + spacing: 8, + children: [ + buildRow( + title: 'وضعیت بازرسی', + value: controller.getStatus(item), + titleStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + valueStyle: AppFonts.yekan14.copyWith( + color: controller.getStatusColor(item), + ), + ), + if (item.hatching?.poultry?.unitName != null) + buildRow( + title: 'مرغداری', + value: item.hatching?.poultry?.unitName ?? '-', + ), + if (item.hatching?.id != null) + buildRow( + title: 'شناسه جوجه‌ریزی', + value: item.hatching!.id.toString(), + ), + + if (item + .reportInformation + ?.technicalOfficer + ?.technicalHealthOfficer != + null) + buildRow( + title: 'کارشناس بهداشت', + value: + item + .reportInformation! + .technicalOfficer! + .technicalHealthOfficer ?? + '-', + ), + if (item + .reportInformation + ?.technicalOfficer + ?.technicalEngineeringOfficer != + null) + buildRow( + title: 'کارشناس فنی', + value: + item + .reportInformation! + .technicalOfficer! + .technicalEngineeringOfficer ?? + '-', + ), + if (item.reportInformation?.casualties?.normalLosses != null || + item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات عادی', + value: (item.reportInformation?.casualties?.normalLosses ?? 0) + .toString(), + unit: '(قطعه)', + ), + if (item.reportInformation?.casualties?.abnormalLosses != null) + buildUnitRow( + title: 'تلفات غیرعادی', + value: item.reportInformation!.casualties!.abnormalLosses + .toString(), + unit: '(قطعه)', + ), + + if (item.reportInformation?.inspectionNotes != null && + item.reportInformation!.inspectionNotes!.isNotEmpty) + buildRow( + title: 'یادداشت بازرسی', + value: item.reportInformation!.inspectionNotes ?? '-', + ), + + RElevated( + text: 'جزییات', + isFullWidth: true, + width: 150.w, + height: 40.h, + onPressed: () { + showDetailsBottomSheet(item); + }, + textStyle: AppFonts.yekan16.copyWith(color: Colors.white), + backgroundColor: AppColor.greenNormal, + ), + ], + ), + ); + } + + Row itemListWidgetReport(PoultryScienceReport item) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox(width: 20), + Expanded( + flex: 2, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 5, + children: [ + Text( + 'شناسه جوجه‌ریزی: ${item.hatching?.id ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + + Text( + item.createDate?.toJalali.formatCompactDate() ?? '-', + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + Expanded( + flex: 3, + child: Column( + spacing: 5, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'مرغداری: ${item.hatching?.poultry?.unitName ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal), + ), + if (item.reportInformation?.inspectionStatus != null) + Text( + 'وضعیت بازرسی: ${item.reportInformation?.inspectionStatus ?? '-'}', + textAlign: TextAlign.center, + style: AppFonts.yekan12.copyWith(color: AppColor.bgIcon), + ), + ], + ), + ), + ], + ); + } + + void showDetailsBottomSheet(PoultryScienceReport item) { + Get.bottomSheet( + isScrollControlled: true, + BaseBottomSheet( + height: Get.height * 0.8, + rootChild: DetailsBottomSheetWidget(item: item), + ), + ); + } + + 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.getReport(); + Get.back(); + }, + height: 40, + ), + ], + ), + ); + } +} + +class DetailsBottomSheetWidget extends StatefulWidget { + final PoultryScienceReport item; + + const DetailsBottomSheetWidget({super.key, required this.item}); + + @override + State createState() => + _DetailsBottomSheetWidgetState(); +} + +class _DetailsBottomSheetWidgetState extends State { + int selectedTabIndex = 0; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 10, + ), + child: Text( + 'جزییات', + style: AppFonts.yekan18Bold.copyWith( + color: AppColor.iconColor, + ), + ), + ), + ], + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + tabBarWidget( + [ + 'اطلاعات کلی', + 'شرایط سالن', + 'تلفات', + 'کارشناس', + 'نهاده', + 'زیرساخت', + 'نیروی انسانی', + 'تسهیلات', + ], + selectedTabIndex, + (index) => setState(() => selectedTabIndex = index), + ), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _buildTableContent(), + ), + ), + ), + ], + ); + } + + Widget _buildTableContent() { + switch (selectedTabIndex) { + case 0: + return generalInfoTable(); + case 1: + return generalConditionHallTable(); + case 2: + return casualtiesTable(); + case 3: + return technicalOfficerTable(); + case 4: + return inputStatusTable(); + case 5: + return infrastructureEnergyTable(); + case 6: + return hrTable(); + case 7: + return facilitiesTable(); + default: + return generalInfoTable(); + } + } + + Widget technicalOfficerTable() { + final officer = widget.item.reportInformation?.technicalOfficer; + if (officer == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'کارشناس فنی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'کارشناس بهداشت', + value: officer.technicalHealthOfficer ?? '-', + ), + rTableRow( + title: 'کارشناس فنی', + value: officer.technicalEngineeringOfficer ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget generalInfoTable() { + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'اطلاعات کلی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت بازرسی', + value: + widget.item.reportInformation?.inspectionStatus ?? + widget.item.state ?? + '-', + ), + rTableRow( + title: 'یادداشت بازرسی', + value: widget.item.reportInformation?.inspectionNotes ?? '-', + ), + rTableRow(title: 'نقش', value: widget.item.reporterRole ?? '-'), + if (widget.item.lat != null && widget.item.log != null) + rTableRow( + title: 'موقعیت', + value: '${widget.item.lat}, ${widget.item.log}', + ), + if (widget.item.hatching?.id != null) + rTableRow( + title: 'شناسه جوجه ریزی', + value: widget.item.hatching!.id.toString(), + ), + ], + ), + ), + ], + ); + } + + Widget generalConditionHallTable() { + final hall = widget.item.reportInformation?.generalConditionHall; + if (hall == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'شرایط عمومی سالن', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow(title: 'وضعیت سلامت', value: hall.healthStatus ?? '-'), + rTableRow( + title: 'وضعیت تهویه', + value: hall.ventilationStatus ?? '-', + ), + rTableRow(title: 'وضعیت بستر', value: hall.bedCondition ?? '-'), + rTableRow(title: 'دما', value: hall.temperature ?? '-'), + rTableRow( + title: 'منبع آب آشامیدنی', + value: hall.drinkingWaterSource ?? '-', + ), + rTableRow( + title: 'کیفیت آب آشامیدنی', + value: hall.drinkingWaterQuality ?? '-', + ), + ], + ), + ), + if (hall.images != null && hall.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: hall.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(hall.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(hall.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget casualtiesTable() { + final casualties = widget.item.reportInformation?.casualties; + if (casualties == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تلفات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (casualties.normalLosses != null) + rTableRow( + title: 'تلفات عادی', + value: casualties.normalLosses.toString(), + ), + if (casualties.abnormalLosses != null) + rTableRow( + title: 'تلفات غیرعادی', + value: casualties.abnormalLosses.toString(), + ), + rTableRow( + title: 'منبع جوجه ریزی', + value: casualties.sourceOfHatching ?? '-', + ), + rTableRow( + title: 'علت تلفات غیرعادی', + value: casualties.causeAbnormalLosses ?? '-', + ), + rTableRow( + title: 'نوع بیماری', + value: casualties.typeDisease ?? '-', + ), + rTableRow( + title: 'نمونه‌برداری انجام شده', + value: casualties.samplingDone == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع نمونه‌برداری', + value: casualties.typeSampling ?? '-', + ), + ], + ), + ), + if (casualties.images != null && casualties.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: casualties.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(casualties.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(casualties.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget inputStatusTable() { + final inputStatus = widget.item.reportInformation?.inputStatus; + if (inputStatus == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'وضعیت نهاده', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'وضعیت نهاده', + value: inputStatus.inputStatus ?? '-', + ), + rTableRow( + title: 'نام شرکت', + value: inputStatus.companyName ?? '-', + ), + rTableRow( + title: 'کد پیگیری', + value: inputStatus.trackingCode ?? '-', + ), + rTableRow( + title: 'نوع دانه', + value: inputStatus.typeOfGrain ?? '-', + ), + rTableRow( + title: 'موجودی در انبار', + value: inputStatus.inventoryInWarehouse ?? '-', + ), + rTableRow( + title: 'موجودی تا بازدید', + value: inputStatus.inventoryUntilVisit ?? '-', + ), + rTableRow( + title: 'درجه دانه', + value: inputStatus.gradeGrain ?? '-', + ), + ], + ), + ), + if (inputStatus.images != null && inputStatus.images!.isNotEmpty) ...[ + SizedBox(height: 16), + Text( + 'تصاویر', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + SizedBox(height: 10), + SizedBox( + height: 100.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: inputStatus.images!.length, + separatorBuilder: (context, index) => SizedBox(width: 10), + itemBuilder: (context, index) => GestureDetector( + onTap: () => showImageDialog(inputStatus.images!, index), + child: Container( + width: 80.w, + height: 80.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(inputStatus.images![index]), + ), + ), + ), + ), + ), + ), + ], + ], + ); + } + + Widget infrastructureEnergyTable() { + final infra = widget.item.reportInformation?.infrastructureEnergy; + if (infra == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'زیرساخت و انرژی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'نوع ژنراتور', + value: infra.generatorType ?? '-', + ), + rTableRow( + title: 'مدل ژنراتور', + value: infra.generatorModel ?? '-', + ), + rTableRow( + title: 'تعداد ژنراتور', + value: infra.generatorCount ?? '-', + ), + rTableRow( + title: 'ظرفیت ژنراتور', + value: infra.generatorCapacity ?? '-', + ), + rTableRow(title: 'نوع سوخت', value: infra.fuelType ?? '-'), + rTableRow( + title: 'عملکرد ژنراتور', + value: infra.generatorPerformance ?? '-', + ), + rTableRow( + title: 'موجودی سوخت اضطراری', + value: infra.emergencyFuelInventory ?? '-', + ), + rTableRow( + title: 'تاریخچه قطع برق', + value: infra.hasPowerCutHistory == true ? 'بله' : 'خیر', + ), + if (infra.hasPowerCutHistory == true) ...[ + rTableRow( + title: 'مدت قطع برق', + value: infra.powerCutDuration ?? '-', + ), + rTableRow( + title: 'ساعت قطع برق', + value: infra.powerCutHour ?? '-', + ), + ], + rTableRow( + title: 'یادداشت اضافی', + value: infra.additionalNotes ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget hrTable() { + final hr = widget.item.reportInformation?.hr; + if (hr == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'نیروی انسانی', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + if (hr.numberEmployed != null) + rTableRow( + title: 'تعداد شاغلین', + value: hr.numberEmployed.toString(), + ), + if (hr.numberIndigenous != null) + rTableRow( + title: 'تعداد بومی', + value: hr.numberIndigenous.toString(), + ), + if (hr.numberNonIndigenous != null) + rTableRow( + title: 'تعداد غیربومی', + value: hr.numberNonIndigenous.toString(), + ), + rTableRow( + title: 'وضعیت قرارداد', + value: hr.contractStatus ?? '-', + ), + rTableRow( + title: 'آموزش دیده', + value: hr.trained == true ? 'بله' : 'خیر', + ), + ], + ), + ), + ], + ); + } + + Widget facilitiesTable() { + final facilities = widget.item.reportInformation?.facilities; + if (facilities == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'اطلاعاتی وجود ندارد', + style: AppFonts.yekan14.copyWith(color: AppColor.textColor), + ), + ), + ); + } + + return Column( + children: [ + SizedBox(height: 10), + Row( + children: [ + Text( + 'تسهیلات', + style: AppFonts.yekan16Bold.copyWith(color: AppColor.iconColor), + ), + ], + ), + SizedBox(height: 10), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + rTableRow( + title: 'دارای تسهیلات', + value: facilities.hasFacilities == true ? 'بله' : 'خیر', + ), + rTableRow( + title: 'نوع تسهیلات', + value: facilities.typeOfFacility ?? '-', + ), + if (facilities.amount != null) + rTableRow(title: 'مبلغ', value: facilities.amount.toString()), + rTableRow(title: 'تاریخ', value: facilities.date ?? '-'), + rTableRow( + title: 'وضعیت بازپرداخت', + value: facilities.repaymentStatus ?? '-', + ), + rTableRow( + title: 'درخواست تسهیلات', + value: facilities.requestFacilities ?? '-', + ), + ], + ), + ), + ], + ); + } + + Widget tabBarWidget( + List tabs, + int selectedIndex, + Function(int) onTabSelected, + ) { + return SizedBox( + height: 40.h, + width: Get.width, + child: Column( + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + reverse: true, + child: Row( + children: [ + ...tabs.map( + (tab) => GestureDetector( + onTap: () => onTabSelected(tabs.indexOf(tab)), + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 11, + ), + decoration: BoxDecoration( + border: tab == tabs[selectedIndex] + ? Border( + bottom: BorderSide( + color: AppColor.blueNormalOld, + width: 3, + ), + ) + : null, + ), + child: Text( + tab, + style: AppFonts.yekan12Bold.copyWith( + color: tab == tabs[selectedIndex] + ? AppColor.blueNormalOld + : AppColor.mediumGrey, + ), + ), + ), + ), + ), + ], + ), + ), + Divider(color: AppColor.blackLightHover, height: 1, thickness: 1), + ], + ), + ); + } + + Row rTableRow({String? title, String? value}) { + return Row( + children: [ + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + color: AppColor.bgLight2, + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + title ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.iconColor), + ), + ), + ), + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 9, vertical: 11), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: AppColor.blackLightHover, width: 1), + ), + ), + child: Text( + value ?? '', + style: AppFonts.yekan14Bold.copyWith(color: AppColor.textColor), + ), + ), + ), + ], + ); + } + + void showImageDialog(List images, int initialIndex) { + final pageController = PageController(initialPage: initialIndex); + final currentIndex = initialIndex.obs; + + Get.dialog( + Dialog( + backgroundColor: Colors.transparent, + insetPadding: EdgeInsets.zero, + child: Container( + width: Get.width, + height: Get.height, + color: Colors.black, + child: Stack( + children: [ + PageView.builder( + controller: pageController, + itemCount: images.length, + onPageChanged: (index) { + currentIndex.value = index; + }, + itemBuilder: (context, index) { + return InteractiveViewer( + minScale: 0.5, + maxScale: 4.0, + child: Center( + child: Image.network( + images[index], + fit: BoxFit.contain, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + color: Colors.white, + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Center( + child: Icon( + Icons.error, + color: Colors.white, + size: 50, + ), + ); + }, + ), + ), + ); + }, + ), + Positioned( + top: 40, + right: 16, + child: IconButton( + icon: Icon(Icons.close, color: Colors.white, size: 30), + onPressed: () => Get.back(), + ), + ), + if (images.length > 1) + Positioned( + bottom: 20, + left: 0, + right: 0, + child: Center( + child: Obx( + () => Container( + padding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '${currentIndex.value + 1} / ${images.length}', + style: TextStyle(color: Colors.white, fontSize: 14), + ), + ), + ), + ), + ), + ], + ), + ), + ), + barrierDismissible: true, + ); + } +} diff --git a/packages/chicken/lib/features/vet_farm/presentation/pages/root/logic.dart b/packages/chicken/lib/features/vet_farm/presentation/pages/root/logic.dart new file mode 100644 index 0000000..c77df23 --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/presentation/pages/root/logic.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:rasadyar_chicken/data/di/chicken_di.dart'; +import 'package:rasadyar_chicken/features/vet_farm/data/repositories/vet_farm_repository.dart'; + +import 'package:rasadyar_chicken/features/common/presentation/page/profile/view.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/routes/pages.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/utils/utils.dart'; +import 'package:rasadyar_core/core.dart'; + +enum ErrorLocationType { serviceDisabled, permissionDenied, none } + +class VetFarmRootLogic extends GetxController { + var tokenService = Get.find(); + + late VetFarmRepository vetFarmRepository; + + RxList errorLocationType = RxList(); + RxMap homeExpandedList = RxMap(); + DateTime? _lastBackPressed; + + RxInt currentPage = 0.obs; + + final pages = [ + Navigator( + key: Get.nestedKey(vetFarmActionKey), + onGenerateRoute: (settings) { + final page = VetFarmPages.pages.firstWhere( + (e) => e.name == settings.name, + orElse: () => VetFarmPages.pages.firstWhere( + (e) => e.name == VetFarmRoutes.homeVetFarm, + ), + ); + + return buildRouteFromGetPage(page); + }, + ), + + ProfilePage(), + ]; + + @override + void onInit() { + super.onInit(); + vetFarmRepository = diChicken.get(); + } + + void toggleExpanded(int index) { + if (homeExpandedList.keys.contains(index)) { + homeExpandedList.remove(index); + } else { + homeExpandedList[index] = false; + } + } + + void rootErrorHandler(DioException error) { + handleGeneric(error, () { + tokenService.deleteModuleTokens(Module.chicken); + }); + } + + void changePage(int index) { + currentPage.value = index; + } + + void popBackTaped() async { + 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(); + } + } +} diff --git a/packages/chicken/lib/features/vet_farm/presentation/pages/root/view.dart b/packages/chicken/lib/features/vet_farm/presentation/pages/root/view.dart new file mode 100644 index 0000000..8e905d6 --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/presentation/pages/root/view.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart'; +import 'package:rasadyar_core/core.dart'; + +import 'logic.dart'; + +class VetFarmRootPage extends GetView { + const VetFarmRootPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChickenBasePage( + isFullScreen: true, + onPopScopTaped: controller.popBackTaped, + child: ObxValue((data) { + return Stack( + children: [ + IndexedStack(children: controller.pages, index: data.value), + Positioned( + right: 0, + left: 0, + bottom: 0, + child: RBottomNavigation( + mainAxisAlignment: MainAxisAlignment.spaceAround, + items: [ + + RBottomNavigationItem( + label: 'خانه', + icon: Assets.vec.homeSvg.path, + isSelected: controller.currentPage.value == 0, + onTap: () { + Get.nestedKey( + vetFarmActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(0); + }, + ), + RBottomNavigationItem( + label: 'پروفایل', + icon: Assets.vec.profileCircleSvg.path, + isSelected: controller.currentPage.value == 1, + onTap: () { + Get.nestedKey( + vetFarmActionKey, + )?.currentState?.popUntil((route) => route.isFirst); + controller.changePage(1); + }, + ), + ], + ), + ), + ], + ); + }, controller.currentPage), + ); + } +} diff --git a/packages/chicken/lib/features/vet_farm/presentation/routes/pages.dart b/packages/chicken/lib/features/vet_farm/presentation/routes/pages.dart new file mode 100644 index 0000000..8763519 --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/presentation/routes/pages.dart @@ -0,0 +1,74 @@ +import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/home/logic.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/home/view.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/root/logic.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/root/view.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/active_hatching/logic.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/active_hatching/view.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/new_inspection/logic.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/new_inspection/view.dart'; +import 'package:rasadyar_chicken/features/vet_farm/presentation/routes/routes.dart'; +import 'package:rasadyar_chicken/presentation/routes/global_binding.dart'; +import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart'; +import 'package:rasadyar_core/core.dart'; + +class VetFarmPages { + VetFarmPages._(); + + static List get pages => [ + GetPage( + name: VetFarmRoutes.initVetFarm, + page: () => VetFarmRootPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ChickenBaseLogic(), fenix: true); + Get.lazyPut(() => VetFarmRootLogic()); + Get.lazyPut(() => VetFarmHomeLogic()); + }), + ], + ), + GetPage( + name: VetFarmRoutes.homeVetFarm, + page: () => HomePage(), + middlewares: [AuthMiddleware()], + binding: BindingsBuilder(() { + Get.put(VetFarmHomeLogic()); + Get.lazyPut(() => ChickenBaseLogic()); + }), + ), + GetPage( + name: VetFarmRoutes.actionVetFarm, + page: () => HomePage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => VetFarmHomeLogic()); + }), + ], + ), + GetPage( + name: VetFarmRoutes.activeHatchingVetFarm, + page: () => ActiveHatchingPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => ActiveHatchingLogic()); + }), + ], + ), + GetPage( + name: VetFarmRoutes.newInspectionVetFarm, + page: () => NewInspectionPage(), + middlewares: [AuthMiddleware()], + bindings: [ + GlobalBinding(), + BindingsBuilder(() { + Get.lazyPut(() => NewInspectionLogic()); + }), + ], + ), + ]; +} diff --git a/packages/chicken/lib/features/vet_farm/presentation/routes/routes.dart b/packages/chicken/lib/features/vet_farm/presentation/routes/routes.dart new file mode 100644 index 0000000..8021045 --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/presentation/routes/routes.dart @@ -0,0 +1,10 @@ +sealed class VetFarmRoutes { + VetFarmRoutes._(); + + static const _base = '/chicken/vetFarm'; + static const initVetFarm = '$_base/'; + static const homeVetFarm = '$_base/home'; + static const actionVetFarm = '$_base/action'; + static const activeHatchingVetFarm = '$_base/activeHatching'; + static const newInspectionVetFarm = '$_base/newInspection'; +} diff --git a/packages/chicken/lib/features/vet_farm/vet_farm.dart b/packages/chicken/lib/features/vet_farm/vet_farm.dart new file mode 100644 index 0000000..258e885 --- /dev/null +++ b/packages/chicken/lib/features/vet_farm/vet_farm.dart @@ -0,0 +1,3 @@ +export 'data/di/vet_farm_di.dart'; +export 'presentation/routes/routes.dart'; +export 'presentation/routes/pages.dart'; diff --git a/packages/chicken/lib/presentation/routes/pages.dart b/packages/chicken/lib/presentation/routes/pages.dart index b6d00b4..7e2ad8a 100644 --- a/packages/chicken/lib/presentation/routes/pages.dart +++ b/packages/chicken/lib/presentation/routes/pages.dart @@ -8,6 +8,13 @@ import 'package:rasadyar_chicken/presentation/pages/kill_house/submit_request/vi import 'package:rasadyar_chicken/features/poultry_farm_inspection/poultry_farm_inspection.dart'; import 'package:rasadyar_chicken/features/poultry_science/poultry_science.dart'; import 'package:rasadyar_chicken/features/steward/steward.dart'; +import 'package:rasadyar_chicken/features/province_operator/province_operator.dart'; +import 'package:rasadyar_chicken/features/province_inspector/province_inspector.dart'; +import 'package:rasadyar_chicken/features/city_jahad/city_jahad.dart'; +import 'package:rasadyar_chicken/features/vet_farm/vet_farm.dart'; +import 'package:rasadyar_chicken/features/super_admin/super_admin.dart'; +import 'package:rasadyar_chicken/features/province_supervisor/province_supervisor.dart'; +import 'package:rasadyar_chicken/features/jahad/jahad.dart'; import 'package:rasadyar_chicken/presentation/routes/global_binding.dart'; import 'package:rasadyar_chicken/presentation/routes/routes.dart'; import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart'; @@ -29,6 +36,34 @@ sealed class ChickenPages { ...PoultrySciencePages.pages, //endregion + //region Province Operator Pages + ...ProvinceOperatorPages.pages, + //endregion + + //region Province Inspector Pages + ...ProvinceInspectorPages.pages, + //endregion + + //region City Jahad Pages + ...CityJahadPages.pages, + //endregion + + //region Vet Farm Pages + ...VetFarmPages.pages, + //endregion + + //region Super Admin Pages + ...SuperAdminPages.pages, + //endregion + + //region Province Supervisor Pages + ...ProvinceSupervisorPages.pages, + //endregion + + //region Jahad Pages + ...JahadPages.pages, + //endregion + //region Poultry Farm Inspection GetPage( name: ChickenRoutes.poultryFarmInspectionHome, diff --git a/packages/chicken/lib/presentation/utils/nested_keys_utils.dart b/packages/chicken/lib/presentation/utils/nested_keys_utils.dart index 9373e8c..0371d0c 100644 --- a/packages/chicken/lib/presentation/utils/nested_keys_utils.dart +++ b/packages/chicken/lib/presentation/utils/nested_keys_utils.dart @@ -12,10 +12,6 @@ const int poultrySecondKey = 106; const int poultryThirdKey = 107; //endregion - - - - //region kill house Keys const int killHouseActionKey = 108; @@ -25,3 +21,38 @@ const int killHouseActionKey = 108; const int poultryScienceActionKey = 109; //endregion + +//region province operator Keys +const int provinceOperatorActionKey = 110; + +//endregion + +//region province inspector Keys +const int provinceInspectorActionKey = 111; + +//endregion + +//region city jahad Keys +const int cityJahadActionKey = 112; + +//endregion + +//region vet farm Keys +const int vetFarmActionKey = 113; + +//endregion + +//region super admin Keys +const int superAdminActionKey = 114; + +//endregion + +//region province supervisor Keys +const int provinceSupervisorActionKey = 115; + +//endregion + +//region jahad Keys +const int jahadActionKey = 116; + +//endregion diff --git a/pubspec.yaml b/pubspec.yaml index 91f4110..fbbe830 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: rasadyar_app description: "A new Flutter project." publish_to: 'none' -version: 1.3.35+32 +version: 1.3.36+32 environment: sdk: ^3.9.2