// UI: Cleaner Search Details View import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/ppm/recurrent_wo.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/views/widgets/qr/scan_qr.dart'; import 'package:test_sa/views/widgets/requests/request_status.dart'; import '../models/cleaner_task_search_result.dart'; import '../provider/cleaner_provider.dart'; import '../../pm_module/recurrent_wo/recurrent_work_order_view.dart'; import 'package:test_sa/controllers/providers/api/all_requests_provider.dart'; import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; import 'package:test_sa/controllers/api_routes/api_manager.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:http/http.dart' as http; class CleanerSearchDetailsView extends StatefulWidget { static const String routeName = '/cleaner_search_details'; final int requestId; final CleanerTaskSearchResult? taskResult; const CleanerSearchDetailsView({ Key? key, required this.requestId, this.taskResult, }) : super(key: key); @override _CleanerSearchDetailsViewState createState() => _CleanerSearchDetailsViewState(); } class _CleanerSearchDetailsViewState extends State { late CleanerProvider _cleanerProvider; // bool _isQRScanned = false; bool _isRequestOpen = false; CleanerTaskSearchResult? _taskData; RecurrentWoData? _recurrentWoData; bool _isLoading = false; @override void initState() { super.initState(); _cleanerProvider = Provider.of(context, listen: false); _taskData = widget.taskResult; WidgetsBinding.instance.addPostFrameCallback((_) { getInitialData(); }); } Future getInitialData() async { // If taskResult is null, fetch data from recurrent work order API if (widget.taskResult == null) { setState(() { _isLoading = true; }); try { // Use direct API call to get better error messages final result = await _getRecurrentWoByIdWithMessage(widget.requestId); if (result["success"] == true && result["data"] != null) { _recurrentWoData = result["data"]; _isRequestOpen = _recurrentWoData?.status?.value == 4; _taskData = CleanerTaskSearchResult( id: _recurrentWoData!.id, taskNo: _recurrentWoData!.taskNo, siteName: _recurrentWoData!.site?.siteName, buildingName: _recurrentWoData!.building?.name, floorName: _recurrentWoData!.floor?.name, departmentName: _recurrentWoData!.department?.name, roomName: _recurrentWoData!.room?.name, locationDescription: null, createdDate: _recurrentWoData!.scheduleDate, ); } else { // Show exact backend error message if (mounted) { _showErrorDialog(context, result["message"] ?? "Failed to load request details. Please try again."); } } } catch (e) { debugPrint('Error fetching recurrent work order: $e'); if (mounted) { _showErrorDialog(context, "Failed to load request details. Please check your connection and try again."); } } finally { setState(() { _isLoading = false; }); } } else { try { await _cleanerProvider.getCleanerRequestById(id: widget.requestId); } catch (e) { debugPrint('Error fetching cleaner request: $e'); if (mounted) { _showErrorDialog(context, "Failed to load request details. Please check your connection and try again."); } } } } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: DefaultAppBar( title: 'Request Details'.addTranslation, onBackPress: () => Navigator.pop(context), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : SafeArea( child: Selector( selector: (_, model) => model.isLoading, builder: (_, isLoading, __) { if (isLoading) { return const Center(child: CircularProgressIndicator()); } return Column( children: [ SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildCleanerRequestDetailCard(context), ], ), ).expanded, FooterActionButton.footerContainer( context: context, child: Column( mainAxisSize: MainAxisSize.min, children: [ if (_isRequestOpen) ...[ Row( children: [ Icon(Icons.info_outline_rounded, size: 16, color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), 8.width, Expanded( child: Text( 'By scanning the QR code you will accept this task'.addTranslation, // textAlign: TextAlign.center, style: AppTextStyles.bodyText2.copyWith( color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, ), ), ), ], ), 8.height, ], AppFilledButton( label: _isRequestOpen ? 'Scan QR Code' : 'Update', maxWidth: true, buttonColor: AppColor.primary10, onPressed: () async { if (_isRequestOpen) { final result = await Navigator.push(context, MaterialPageRoute(builder: (context) => const ScanQr())); if (result != null && result is String && result.isNotEmpty) { showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); try { final qrResult = await CleanerProvider().checkQRSameRoomWithMessage(planRecurrentTaskId: widget.requestId, qrCode: result); if (mounted) Navigator.pop(context); final isValid = qrResult["success"] ?? false; final backendMessage = qrResult["message"] ?? "Unknown error occurred"; if (isValid) { if (mounted) { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: widget.requestId)), ); } } else { if (mounted) { // Show exact backend error message instead of our custom message _showErrorDialog(context, backendMessage); } } } catch (e) { if (mounted) Navigator.pop(context); if (mounted) { // Extract actual error message from the exception or use generic fallback String errorMessage = _extractErrorMessage(e) ?? 'Failed to validate QR code. Please try again.'; _showErrorDialog(context, errorMessage); } } } } else { Navigator.of(context).push( MaterialPageRoute(builder: (_) => RecurrentWorkOrderView(taskId: widget.requestId)), ); } }, ), ], ), ).toShadowContainer(context, padding: 0, showShadow: false, borderRadius: 0), ], ); }, ), ), ); } Widget _buildCleanerRequestDetailCard(BuildContext context) { final taskResult = _taskData; if (taskResult == null) { return const SizedBox(); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Task Number and Date Row Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Text( // 'Request Information', // style: AppTextStyles.heading4.copyWith( // color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, // ), // ), if (_recurrentWoData?.status?.name != null) StatusLabel( label: _recurrentWoData?.status?.name!, textColor: AppColor.getRequestStatusTextColorByName(context, _recurrentWoData?.status?.name!), backgroundColor: AppColor.getRequestStatusColorByName(context, _recurrentWoData?.status?.name!), ), 8.width, // StatusLabel( // label: taskModel?.taskJobStatus?.name ?? widget.requestDetails?.status!, // textColor: AppColor.getRequestStatusTextColorByName(context, taskModel?.taskJobStatus?.name ?? widget.requestDetails?.status!), // backgroundColor: AppColor.getRequestStatusColorByName(context, taskModel?.taskJobStatus?.name ?? widget.requestDetails?.status!), // ), if (taskResult.createdDate != null) Text( taskResult.createdDate!.toServiceRequestCardFormat, textAlign: TextAlign.end, style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), ), ], ), 8.height, if (_recurrentWoData?.title?.isNotEmpty ?? false) Text( _recurrentWoData?.title ?? "", style: AppTextStyles.heading4.copyWith( color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, ), ), if (taskResult.taskNo != null && taskResult.taskNo!.isNotEmpty) Text( 'Task No: ${taskResult.taskNo!}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), if (taskResult.siteName != null && taskResult.siteName!.isNotEmpty) Text( 'Site: ${taskResult.siteName}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), if (taskResult.buildingName != null && taskResult.buildingName!.isNotEmpty) Text( 'Building: ${taskResult.buildingName}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), if (taskResult.floorName != null && taskResult.floorName!.isNotEmpty) Text( 'Floor: ${taskResult.floorName}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), if (taskResult.departmentName != null && taskResult.departmentName!.isNotEmpty) Text( 'Department: ${taskResult.departmentName}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), if (taskResult.roomName != null && taskResult.roomName!.isNotEmpty) Text( 'Room: ${taskResult.roomName}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), if (taskResult.locationDescription != null && taskResult.locationDescription!.isNotEmpty) Text( 'Location: ${taskResult.locationDescription}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), ], ).expanded, ], ), ], ).paddingOnly(start: 16, end: 16, top: 16, bottom: 8), ], ).toShadowContainer(context, padding: 0); } void _showErrorDialog(BuildContext context, String message) { showDialog( context: context, builder: (context) => ConfirmDialog( message: message, title: 'Error', onTap: () => Navigator.pop(context), ), ); } String? _extractErrorMessage(dynamic error) { // Implement your logic to extract error message from the exception // For example, if the error is a custom exception with a message property: if (error is ExceptionWithMessage) { return error.message; } else if (error is String) { // If the error is a string, return it directly return error; } else if (error is Map) { // If the error is a Map (e.g., parsed JSON), extract the message field return error['message'] ?? error.toString(); } // Fallback to a generic error message return 'An unexpected error occurred. Please try again.'; } /// Custom method to get recurrent work order with proper error message extraction Future> _getRecurrentWoByIdWithMessage(int id) async { try { final response = await ApiManager.instance.get( URLs.getRecurrentPlanByIdUrl + "?planRecurrentTaskId=$id", enableToastMessage: false, // Disable automatic toast ); final responseData = json.decode(response.body); if (response.statusCode >= 200 && response.statusCode < 300) { // Success response final recurrentWoData = RecurrentWoData.fromJson(responseData["data"]); // Apply the same processing as in AllRequestsProvider recurrentWoData.planRecurrentMedicalTaskRooms?.forEach((element) { element.planRecurrentMedicalTaskRoomTabs?.forEach((tabs) { tabs.planRecurrentMedicalTaskRoomTabAttributes?.forEach((attribute) { if (attribute.attribute?.type == "bool" && attribute.attributeValue == null) { attribute.attributeValue = "true"; } }); }); }); return { "success": true, "data": recurrentWoData, "message": "Data loaded successfully", }; } else { // Error response - extract the exact error message from backend return { "success": false, "data": null, "message": responseData["message"] ?? "Failed to load request details", }; } } catch (error) { debugPrint('Error in _getRecurrentWoByIdWithMessage: $error'); return { "success": false, "data": null, "message": "Failed to load request details. Please check your connection and try again.", }; } } } class ExceptionWithMessage implements Exception { final String message; ExceptionWithMessage(this.message); }