feat : segment page

This commit is contained in:
2025-07-15 09:03:11 +03:30
parent f79a3ae46f
commit b1496b1ed0
15 changed files with 63 additions and 63 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

View File

@@ -6,7 +6,7 @@ part 'segmentation_model.g.dart';
@freezed @freezed
abstract class SegmentationModel with _$SegmentationModel { abstract class SegmentationModel with _$SegmentationModel {
const factory SegmentationModel({String? key, Buyer? buyer, DateTime? date, int? weight}) = const factory SegmentationModel({String? key, Buyer? buyer, DateTime? date, int? weight,String? result}) =
_SegmentationModel; _SegmentationModel;
factory SegmentationModel.fromJson(Map<String, dynamic> json) => factory SegmentationModel.fromJson(Map<String, dynamic> json) =>

View File

@@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
/// @nodoc /// @nodoc
mixin _$SegmentationModel { mixin _$SegmentationModel {
String? get key; Buyer? get buyer; DateTime? get date; int? get weight; String? get key; Buyer? get buyer; DateTime? get date; int? get weight; String? get result;
/// Create a copy of SegmentationModel /// Create a copy of SegmentationModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -29,16 +29,16 @@ $SegmentationModelCopyWith<SegmentationModel> get copyWith => _$SegmentationMode
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SegmentationModel&&(identical(other.key, key) || other.key == key)&&(identical(other.buyer, buyer) || other.buyer == buyer)&&(identical(other.date, date) || other.date == date)&&(identical(other.weight, weight) || other.weight == weight)); return identical(this, other) || (other.runtimeType == runtimeType&&other is SegmentationModel&&(identical(other.key, key) || other.key == key)&&(identical(other.buyer, buyer) || other.buyer == buyer)&&(identical(other.date, date) || other.date == date)&&(identical(other.weight, weight) || other.weight == weight)&&(identical(other.result, result) || other.result == result));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,key,buyer,date,weight); int get hashCode => Object.hash(runtimeType,key,buyer,date,weight,result);
@override @override
String toString() { String toString() {
return 'SegmentationModel(key: $key, buyer: $buyer, date: $date, weight: $weight)'; return 'SegmentationModel(key: $key, buyer: $buyer, date: $date, weight: $weight, result: $result)';
} }
@@ -49,7 +49,7 @@ abstract mixin class $SegmentationModelCopyWith<$Res> {
factory $SegmentationModelCopyWith(SegmentationModel value, $Res Function(SegmentationModel) _then) = _$SegmentationModelCopyWithImpl; factory $SegmentationModelCopyWith(SegmentationModel value, $Res Function(SegmentationModel) _then) = _$SegmentationModelCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String? key, Buyer? buyer, DateTime? date, int? weight String? key, Buyer? buyer, DateTime? date, int? weight, String? result
}); });
@@ -66,13 +66,14 @@ class _$SegmentationModelCopyWithImpl<$Res>
/// Create a copy of SegmentationModel /// Create a copy of SegmentationModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? key = freezed,Object? buyer = freezed,Object? date = freezed,Object? weight = freezed,}) { @pragma('vm:prefer-inline') @override $Res call({Object? key = freezed,Object? buyer = freezed,Object? date = freezed,Object? weight = freezed,Object? result = freezed,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
as String?,buyer: freezed == buyer ? _self.buyer : buyer // ignore: cast_nullable_to_non_nullable as String?,buyer: freezed == buyer ? _self.buyer : buyer // ignore: cast_nullable_to_non_nullable
as Buyer?,date: freezed == date ? _self.date : date // ignore: cast_nullable_to_non_nullable as Buyer?,date: freezed == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
as DateTime?,weight: freezed == weight ? _self.weight : weight // ignore: cast_nullable_to_non_nullable as DateTime?,weight: freezed == weight ? _self.weight : weight // ignore: cast_nullable_to_non_nullable
as int?, as int?,result: freezed == result ? _self.result : result // ignore: cast_nullable_to_non_nullable
as String?,
)); ));
} }
/// Create a copy of SegmentationModel /// Create a copy of SegmentationModel
@@ -95,13 +96,14 @@ $BuyerCopyWith<$Res>? get buyer {
@JsonSerializable() @JsonSerializable()
class _SegmentationModel implements SegmentationModel { class _SegmentationModel implements SegmentationModel {
const _SegmentationModel({this.key, this.buyer, this.date, this.weight}); const _SegmentationModel({this.key, this.buyer, this.date, this.weight, this.result});
factory _SegmentationModel.fromJson(Map<String, dynamic> json) => _$SegmentationModelFromJson(json); factory _SegmentationModel.fromJson(Map<String, dynamic> json) => _$SegmentationModelFromJson(json);
@override final String? key; @override final String? key;
@override final Buyer? buyer; @override final Buyer? buyer;
@override final DateTime? date; @override final DateTime? date;
@override final int? weight; @override final int? weight;
@override final String? result;
/// Create a copy of SegmentationModel /// Create a copy of SegmentationModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -116,16 +118,16 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SegmentationModel&&(identical(other.key, key) || other.key == key)&&(identical(other.buyer, buyer) || other.buyer == buyer)&&(identical(other.date, date) || other.date == date)&&(identical(other.weight, weight) || other.weight == weight)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _SegmentationModel&&(identical(other.key, key) || other.key == key)&&(identical(other.buyer, buyer) || other.buyer == buyer)&&(identical(other.date, date) || other.date == date)&&(identical(other.weight, weight) || other.weight == weight)&&(identical(other.result, result) || other.result == result));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,key,buyer,date,weight); int get hashCode => Object.hash(runtimeType,key,buyer,date,weight,result);
@override @override
String toString() { String toString() {
return 'SegmentationModel(key: $key, buyer: $buyer, date: $date, weight: $weight)'; return 'SegmentationModel(key: $key, buyer: $buyer, date: $date, weight: $weight, result: $result)';
} }
@@ -136,7 +138,7 @@ abstract mixin class _$SegmentationModelCopyWith<$Res> implements $SegmentationM
factory _$SegmentationModelCopyWith(_SegmentationModel value, $Res Function(_SegmentationModel) _then) = __$SegmentationModelCopyWithImpl; factory _$SegmentationModelCopyWith(_SegmentationModel value, $Res Function(_SegmentationModel) _then) = __$SegmentationModelCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String? key, Buyer? buyer, DateTime? date, int? weight String? key, Buyer? buyer, DateTime? date, int? weight, String? result
}); });
@@ -153,13 +155,14 @@ class __$SegmentationModelCopyWithImpl<$Res>
/// Create a copy of SegmentationModel /// Create a copy of SegmentationModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? key = freezed,Object? buyer = freezed,Object? date = freezed,Object? weight = freezed,}) { @override @pragma('vm:prefer-inline') $Res call({Object? key = freezed,Object? buyer = freezed,Object? date = freezed,Object? weight = freezed,Object? result = freezed,}) {
return _then(_SegmentationModel( return _then(_SegmentationModel(
key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
as String?,buyer: freezed == buyer ? _self.buyer : buyer // ignore: cast_nullable_to_non_nullable as String?,buyer: freezed == buyer ? _self.buyer : buyer // ignore: cast_nullable_to_non_nullable
as Buyer?,date: freezed == date ? _self.date : date // ignore: cast_nullable_to_non_nullable as Buyer?,date: freezed == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
as DateTime?,weight: freezed == weight ? _self.weight : weight // ignore: cast_nullable_to_non_nullable as DateTime?,weight: freezed == weight ? _self.weight : weight // ignore: cast_nullable_to_non_nullable
as int?, as int?,result: freezed == result ? _self.result : result // ignore: cast_nullable_to_non_nullable
as String?,
)); ));
} }

View File

@@ -16,6 +16,7 @@ _SegmentationModel _$SegmentationModelFromJson(Map<String, dynamic> json) =>
? null ? null
: DateTime.parse(json['date'] as String), : DateTime.parse(json['date'] as String),
weight: (json['weight'] as num?)?.toInt(), weight: (json['weight'] as num?)?.toInt(),
result: json['result'] as String?,
); );
Map<String, dynamic> _$SegmentationModelToJson(_SegmentationModel instance) => Map<String, dynamic> _$SegmentationModelToJson(_SegmentationModel instance) =>
@@ -24,6 +25,7 @@ Map<String, dynamic> _$SegmentationModelToJson(_SegmentationModel instance) =>
'buyer': instance.buyer, 'buyer': instance.buyer,
'date': instance.date?.toIso8601String(), 'date': instance.date?.toIso8601String(),
'weight': instance.weight, 'weight': instance.weight,
'result': instance.result,
}; };
_Buyer _$BuyerFromJson(Map<String, dynamic> json) => _Buyer( _Buyer _$BuyerFromJson(Map<String, dynamic> json) => _Buyer(

View File

@@ -152,5 +152,5 @@ abstract class ChickenRepository {
Future<void> editSegmentation({required String token, required SegmentationModel model}); Future<void> editSegmentation({required String token, required SegmentationModel model});
Future<void> deleteSegmentation({required String token, required String key}); Future<SegmentationModel?> deleteSegmentation({required String token, required String key});
} }

View File

@@ -469,11 +469,14 @@ class ChickenRepositoryImpl implements ChickenRepository {
} }
@override @override
Future<void> deleteSegmentation({required String token, required String key}) async { Future<SegmentationModel?> deleteSegmentation({required String token, required String key}) async {
await _httpClient.delete( var res = await _httpClient.delete(
'/app-segmentation/0/', '/app-segmentation/0/',
queryParameters: {'key': key}, queryParameters: {'key': key},
headers: {'Authorization': 'Bearer $token'}, headers: {'Authorization': 'Bearer $token'},
fromJson: (json) => SegmentationModel.fromJson(json),
); );
return res.data;
} }
} }

View File

@@ -31,7 +31,7 @@ class BuyOutOfProvincePage extends GetView<BuyOutOfProvinceLogic> {
return RPaginatedListView( return RPaginatedListView(
listType: ListType.separated, listType: ListType.separated,
resource: data.value, resource: data.value,
hasMore:data.value.data?.next!=null , hasMore: data.value.data?.next != null,
padding: EdgeInsets.fromLTRB(8, 8, 8, 80), padding: EdgeInsets.fromLTRB(8, 8, 8, 80),
itemBuilder: (context, index) { itemBuilder: (context, index) {
var item = data.value.data!.results![index]; var item = data.value.data!.results![index];
@@ -356,17 +356,10 @@ class BuyOutOfProvincePage extends GetView<BuyOutOfProvinceLogic> {
}, },
), ),
RTextField( UnitTextField(
controller: controller.carcassWeightController, controller: controller.carcassWeightController,
label: 'وزن', hint: 'وزن',
filled: true, unit: 'کیلوگرم',
suffixIcon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Assets.vec.killogramSvg.svg(),
),
filledColor: AppColor.bgLight,
keyboardType: TextInputType.number,
borderColor: AppColor.darkGreyLight,
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter.digitsOnly, FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(), SeparatorInputFormatter(),
@@ -589,10 +582,8 @@ class BuyOutOfProvincePage extends GetView<BuyOutOfProvinceLogic> {
}, },
height: 40, height: 40,
), ),
], ],
), ),
); );
} }
} }

View File

@@ -1,4 +1,5 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:rasadyar_auth/data/utils/safe_call.dart'; import 'package:rasadyar_auth/data/utils/safe_call.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart'; import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/segmentation_model/segmentation_model.dart'; import 'package:rasadyar_chicken/data/models/response/segmentation_model/segmentation_model.dart';
@@ -75,10 +76,8 @@ class SegmentationLogic extends GetxController {
void clearForm() { void clearForm() {
weightController.text = '0'; weightController.text = '0';
isSubmitButtonEnabled.value = false;
selectedProduct.value = null;
selectedSegment.value = null; selectedSegment.value = null;
formKey.currentState?.reset();
} }
void validateForm() { void validateForm() {
@@ -142,6 +141,7 @@ class SegmentationLogic extends GetxController {
Future<void> deleteSegmentation(String key) async { Future<void> deleteSegmentation(String key) async {
await safeCall( await safeCall(
showError: true,
call: () => rootLogic.chickenRepository.deleteSegmentation( call: () => rootLogic.chickenRepository.deleteSegmentation(
token: rootLogic.tokenService.accessToken.value!, token: rootLogic.tokenService.accessToken.value!,
key: key, key: key,
@@ -152,6 +152,7 @@ class SegmentationLogic extends GetxController {
Future<bool> editSegment() async { Future<bool> editSegment() async {
var res = true; var res = true;
safeCall( safeCall(
showError: true,
call: () async => await rootLogic.chickenRepository.editSegmentation( call: () async => await rootLogic.chickenRepository.editSegmentation(
token: rootLogic.tokenService.accessToken.value!, token: rootLogic.tokenService.accessToken.value!,
model: SegmentationModel( model: SegmentationModel(

View File

@@ -17,7 +17,7 @@ class SegmentationPage extends GetView<SegmentationLogic> {
routes: controller.routesName, routes: controller.routesName,
onSearchChanged: (data) => controller.setSearchValue(data), onSearchChanged: (data) => controller.setSearchValue(data),
filteringWidget: filterBottomSheet(), filteringWidget: filterBottomSheet(),
isBase: true, hasBack: false,
widgets: [ widgets: [
Expanded( Expanded(
child: ObxValue((data) { child: ObxValue((data) {
@@ -41,7 +41,8 @@ class SegmentationPage extends GetView<SegmentationLogic> {
child: itemListWidget(item), child: itemListWidget(item),
secondChild: itemListExpandedWidget(item, index), secondChild: itemListExpandedWidget(item, index),
labelColor: AppColor.blueLight, labelColor: AppColor.blueLight,
labelIcon: Assets.vec.timerSvg.path, labelIconColor: AppColor.customGrey,
labelIcon: Assets.vec.convertCubeSvg.path,
); );
}, controller.isExpandedList); }, controller.isExpandedList);
}, },
@@ -51,11 +52,10 @@ class SegmentationPage extends GetView<SegmentationLogic> {
}, controller.segmentationList), }, controller.segmentationList),
), ),
], ],
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
floatingActionButton: RFab.add( floatingActionButton: RFab.add(
onPressed: () { onPressed: () {
//TODO Get.bottomSheet(addOrEditBottomSheet(), isScrollControlled: true);
//Get.bottomSheet(addOrEditSaleBottomSheet(), isScrollControlled: true);
}, },
), ),
); );
@@ -221,7 +221,7 @@ class SegmentationPage extends GetView<SegmentationLogic> {
Widget addOrEditBottomSheet([bool isOnEdit = false]) { Widget addOrEditBottomSheet([bool isOnEdit = false]) {
return BaseBottomSheet( return BaseBottomSheet(
height: 500.h, height: 300.h,
child: SingleChildScrollView( child: SingleChildScrollView(
child: Form( child: Form(
key: controller.formKey, key: controller.formKey,
@@ -244,18 +244,14 @@ class SegmentationPage extends GetView<SegmentationLogic> {
child: Column( child: Column(
spacing: 12, spacing: 12,
children: [ children: [
RTextField( UnitTextField(
hint: 'وزن',
unit: 'کیلوگرم',
controller: controller.weightController, controller: controller.weightController,
label: 'وزن',
keyboardType: TextInputType.number,
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter.digitsOnly, FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(), SeparatorInputFormatter(),
], ],
validator: (value) { validator: (value) {
if (value == null) { if (value == null) {
return 'لطفاً وزن لاشه را وارد کنید'; return 'لطفاً وزن لاشه را وارد کنید';
@@ -295,13 +291,13 @@ class SegmentationPage extends GetView<SegmentationLogic> {
: null, : null,
height: 40, height: 40,
); );
}, controller.isSaleSubmitButtonEnabled); }, controller.isSubmitButtonEnabled);
} }
Widget _productDropDown() { Widget _productDropDown() {
return Obx(() { return Obx(() {
return OverlayDropdownWidget<ProductModel>( return OverlayDropdownWidget<ProductModel>(
items: controller.rolesProductsModel, items: controller.rootLogic.rolesProductsModel,
height: 56, height: 56,
hasDropIcon: false, hasDropIcon: false,
background: Colors.white, background: Colors.white,
@@ -321,7 +317,7 @@ class SegmentationPage extends GetView<SegmentationLogic> {
Text(item?.name ?? 'انتخاب محصول'), Text(item?.name ?? 'انتخاب محصول'),
Spacer(), Spacer(),
Text( Text(
'موجودی:${controller.rootLogic.inventoryModel.value?.totalRemainWeight.separatedByComma ?? 0}', 'موجودی: ${controller.rootLogic.inventoryModel.value?.totalRemainWeight.separatedByComma ?? 0} کیلوگرم',
), ),
], ],
), ),

View File

@@ -193,7 +193,10 @@ class ListItem2 extends StatelessWidget {
width: 16.w, width: 16.w,
height: 16.h, height: 16.h,
//TODO //TODO
colorFilter: ColorFilter.mode(labelIconColor ?? AppColor.mediumGreyDarkActive, BlendMode.srcIn), colorFilter: ColorFilter.mode(
labelIconColor ?? AppColor.mediumGreyDarkActive,
BlendMode.srcIn,
),
), ),
), ),
), ),

View File

@@ -6,7 +6,6 @@ class DioRemote implements IHttpClient {
late Dio dio; late Dio dio;
final AppInterceptor interceptors; final AppInterceptor interceptors;
DioRemote({this.baseUrl, required this.interceptors}); DioRemote({this.baseUrl, required this.interceptors});
@override @override
@@ -90,6 +89,7 @@ class DioRemote implements IHttpClient {
Map<String, String>? headers, Map<String, String>? headers,
ProgressCallback? onSendProgress, ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress, ProgressCallback? onReceiveProgress,
T Function(Map<String, dynamic> json)? fromJson,
}) async { }) async {
final response = await dio.put( final response = await dio.put(
path, path,
@@ -100,6 +100,11 @@ class DioRemote implements IHttpClient {
onReceiveProgress: onReceiveProgress, onReceiveProgress: onReceiveProgress,
cancelToken: ApiHandler.globalCancelToken, cancelToken: ApiHandler.globalCancelToken,
); );
if (fromJson != null && response.data is Map<String, dynamic>) {
response.data = fromJson(response.data);
return DioResponse<T>(response);
}
return DioResponse<T>(response); return DioResponse<T>(response);
} }
@@ -109,6 +114,7 @@ class DioRemote implements IHttpClient {
dynamic data, dynamic data,
Map<String, dynamic>? queryParameters, Map<String, dynamic>? queryParameters,
Map<String, String>? headers, Map<String, String>? headers,
T Function(Map<String, dynamic> json)? fromJson,
}) async { }) async {
final response = await dio.delete<T>( final response = await dio.delete<T>(
path, path,

View File

@@ -149,6 +149,7 @@ class AppColor {
static const Color mediumGreyDarker = Color( static const Color mediumGreyDarker = Color(
0xFF323232, 0xFF323232,
); // #323232 rgb(50, 50, 50) ); // #323232 rgb(50, 50, 50)
static const Color customGrey = Color(0xFF808081); // #808081 rgb(128, 128, 129)
//endregion //endregion
//region ---Light Grey Colors --- //region ---Light Grey Colors ---

View File

@@ -143,9 +143,6 @@ class $AssetsIconsGen {
/// File path: assets/icons/key.svg /// File path: assets/icons/key.svg
SvgGenImage get key => const SvgGenImage('assets/icons/key.svg'); SvgGenImage get key => const SvgGenImage('assets/icons/key.svg');
/// File path: assets/icons/killogram.svg
SvgGenImage get killogram => const SvgGenImage('assets/icons/killogram.svg');
/// File path: assets/icons/liveStock.svg /// File path: assets/icons/liveStock.svg
SvgGenImage get liveStock => const SvgGenImage('assets/icons/liveStock.svg'); SvgGenImage get liveStock => const SvgGenImage('assets/icons/liveStock.svg');
@@ -285,7 +282,6 @@ class $AssetsIconsGen {
inside, inside,
inspection, inspection,
key, key,
killogram,
liveStock, liveStock,
lock, lock,
logout, logout,
@@ -468,9 +464,6 @@ class $AssetsVecGen {
/// File path: assets/vec/key.svg.vec /// File path: assets/vec/key.svg.vec
SvgGenImage get keySvg => const SvgGenImage.vec('assets/vec/key.svg.vec'); SvgGenImage get keySvg => const SvgGenImage.vec('assets/vec/key.svg.vec');
/// File path: assets/vec/killogram.svg.vec
SvgGenImage get killogramSvg => const SvgGenImage.vec('assets/vec/killogram.svg.vec');
/// File path: assets/vec/liveStock.svg.vec /// File path: assets/vec/liveStock.svg.vec
SvgGenImage get liveStockSvg => const SvgGenImage.vec('assets/vec/liveStock.svg.vec'); SvgGenImage get liveStockSvg => const SvgGenImage.vec('assets/vec/liveStock.svg.vec');
@@ -610,7 +603,6 @@ class $AssetsVecGen {
insideSvg, insideSvg,
inspectionSvg, inspectionSvg,
keySvg, keySvg,
killogramSvg,
liveStockSvg, liveStockSvg,
lockSvg, lockSvg,
logoutSvg, logoutSvg,

View File

@@ -110,6 +110,11 @@ bool _isRetryableError(dynamic error) {
String _getErrorMessage(dynamic error) { String _getErrorMessage(dynamic error) {
if (error is DioException) { if (error is DioException) {
final responseData = error.response?.data;
if (responseData is Map<String, dynamic> && responseData['result'] != null) {
return responseData['result'].toString();
}
switch (error.type) { switch (error.type) {
case DioExceptionType.connectionTimeout: case DioExceptionType.connectionTimeout:
return 'خطا در اتصال - زمان اتصال تمام شد'; return 'خطا در اتصال - زمان اتصال تمام شد';