import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/controllers/providers/api/user_provider.dart'; import 'package:test_sa/dashboard_latest/dashboard_provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/modules/cleaner_module/models/one_time_task_data.dart'; import 'package:test_sa/modules/cleaner_module/provider/cleaner_provider.dart'; import 'package:test_sa/modules/cleaner_module/widgets/cleaner_checklist_widget.dart'; import 'package:test_sa/modules/cleaner_module/pages/one_time_task_preview_screen.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/new_views/pages/land_page/land_page.dart'; import 'package:test_sa/views/widgets/loaders/app_loading.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; import 'package:test_sa/views/widgets/timer/app_timer.dart'; import 'package:test_sa/models/timer_model.dart'; import 'package:test_sa/views/widgets/requests/request_status.dart'; class OneTimeTaskView extends StatefulWidget { static const String routeName = "/one_time_task"; final int taskId; final OneTimeTaskData? initialData; const OneTimeTaskView({Key? key, required this.taskId, this.initialData}) : super(key: key); @override State createState() => _OneTimeTaskViewState(); } class _OneTimeTaskViewState extends State { OneTimeTaskData? _taskData; bool _isLoading = false; DateTime? _timerStart; List? _checklistItems; // Cache checklist items @override void initState() { super.initState(); // Use initial data if provided, otherwise load from API if (widget.initialData != null) { _taskData = widget.initialData; // Initialize imageRooms as empty list if null _taskData?.imageRooms ??= []; // Start timer automatically if task is in progress if (_taskData?.statusValue == 1 && _taskData?.startTime != null) { _timerStart = DateTime.parse(_taskData!.startTime!); } } else { _loadTaskData(); } } Future _loadTaskData() async { setState(() { _isLoading = true; }); try { final result = await CleanerProvider().getOneTimeTaskById(id: widget.taskId); if (result['success'] && result['data'] != null) { setState(() { _taskData = OneTimeTaskData.fromJson(result['data']); // Initialize imageRooms as empty list if null _taskData?.imageRooms ??= []; // Convert attachments from API to imageRooms format for display if ((_taskData?.imageRooms?.isEmpty ?? true) && (_taskData?.attachments != null && _taskData!.attachments!.isNotEmpty)) { _taskData!.imageRooms = _taskData!.attachments! .map((attachment) => URLs.getFileUrl(attachment.name ?? '') ?? '') .where((url) => url.isNotEmpty) .toList(); } _isLoading = false; // Start timer automatically if task is in progress if (_taskData?.statusValue == 1 && _taskData?.startTime != null) { _timerStart = DateTime.parse(_taskData!.startTime!); } }); } else { setState(() { _isLoading = false; }); if (mounted) { _showErrorDialog(context, result['message'] ?? 'Failed to load task'); } } } catch (e) { setState(() { _isLoading = false; }); if (mounted) { _showErrorDialog(context, 'Failed to load task data'); } } } bool get isClosed => _taskData?.statusValue == 2; @override Widget build(BuildContext context) { return Scaffold( appBar: DefaultAppBar( title: 'One Time Task'.addTranslation, onWillPopScope: null, onBackPress: (){ WidgetsBinding.instance.addPostFrameCallback((_) { UserProvider userProvider = Provider.of(context, listen: false); DashBoardProvider dashBoardProvider = Provider.of(context, listen: false); dashBoardProvider.getDashBoardCount(usersType:userProvider.user!.type!); dashBoardProvider.resetRequestDataList(); dashBoardProvider.getRequestDetail(usersType: userProvider.user!.type!, status: dashBoardProvider.tabs[dashBoardProvider.currentListIndex].tag); }); Navigator.pop(context); }, ), body: _isLoading ? const ALoading() : _taskData != null ? SafeArea( child: Column( children: [ Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTaskInfoCard(context), 16.height, CleanerChecklistWidget( isClosed: isClosed, checklistItems: _convertInstructionsToChecklist(), onChecklistChanged: (value) { setState(() { _updateInstructions(value); }); }, capturedImages: _taskData!.imageRooms ?? [], onImagesChanged: (images) { setState(() { _taskData!.imageRooms = images; }); }, ), ], ), ), ), if (!isClosed) ...[ FooterActionButton.footerContainer( context: context, child: AppFilledButton( label: context.translation.complete, buttonColor: AppColor.primary10, onPressed: _onComplete, ), ), ] ], ), ) : NoDataFound(message: context.translation.noDataFound).center, ).handlePopScope( cxt: context, showPopUp: false, onSave: () { Navigator.pop(context); }, ); } Widget _buildTaskInfoCard(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (_taskData?.statusName != null) StatusLabel( radius: 4, label: _taskData!.statusName!, textColor: AppColor.getHistoryLogStatusTextColorByName(_taskData!.statusName!), backgroundColor: AppColor.getHistoryLogStatusColorByName(_taskData!.statusName!), ), if (_taskData?.createdDate != null) Text( _taskData!.createdDate!.toString().toServiceRequestCardFormat, textAlign: TextAlign.end, style: AppTextStyles.tinyFont.copyWith( color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, ), ), ], ), 12.height, if (_taskData?.oneTimeSetting?.taskTypeName != null) Text( _taskData!.oneTimeSetting!.taskTypeName!, style: AppTextStyles.heading4.copyWith( color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, ), ), 8.height, if (_taskData?.oneTimeSetting?.instructionDescriptionName != null) Text( _taskData!.oneTimeSetting!.instructionDescriptionName!, style: AppTextStyles.bodyText.copyWith( color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, ), ), 8.height, if (_taskData?.siteName != null) _buildInfoRow('Site', _taskData!.siteName!), if (_taskData?.buildingName != null) _buildInfoRow('Building', _taskData!.buildingName!), if (_taskData?.floorName != null) _buildInfoRow('Floor', _taskData!.floorName!), if (_taskData?.departmentName != null) _buildInfoRow('Department', _taskData!.departmentName!), if (_taskData?.roomName != null) _buildInfoRow('Room', _taskData!.roomName!), if (_taskData?.locationDescription != null) _buildInfoRow('Location', _taskData!.locationDescription!), // Timer display using AppTimer widget if (_timerStart != null && !isClosed) ...[ 12.height, AppTimer( label: 'Working Time', timer: TimerModel( startAt: _timerStart, endAt: null, durationInSecond: 0, ), enabled: false, // Disable controls, just show the timer showTimer: true, ), ], ], ).paddingOnly(start: 16, end: 16, top: 16, bottom: 16), ], ).toShadowContainer(context, padding: 0); } Widget _buildInfoRow(String label, String value) { return Padding( padding: const EdgeInsets.only(bottom: 4), child: Text( '$label: $value', style: AppTextStyles.bodyText.copyWith( color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, ), ), ); } List _convertInstructionsToChecklist() { // If checklist items are already created, reuse them if (_checklistItems != null) { // Update the existing items with latest data from instructions for (var item in _checklistItems!) { final instruction = _taskData?.instructions?.firstWhere((i) => i.id == item.id, orElse: () => Instruction()); if (instruction?.id != null) { item.isChecked = instruction!.isDone ?? false; // Only update comment if it's different to avoid cursor jumping if (item.comment != (instruction.comment ?? '')) { item.comment = instruction.comment ?? ''; } } } return _checklistItems!; } // Create new checklist items only on first call _checklistItems = _taskData?.instructions?.map((instruction) { return ChecklistItem( id: instruction.id ?? 0, title: instruction.instructionText ?? '', isChecked: instruction.isDone ?? false, isMandatory: instruction.isRequired ?? false, comment: instruction.comment ?? '', ); }).toList() ?? []; return _checklistItems!; } void _updateInstructions(List checklist) { _taskData?.instructions = checklist.map((item) { return Instruction( id: item.id, instructionTextId: _taskData?.instructions?.firstWhere((i) => i.id == item.id).instructionTextId, instructionText: item.title, isDone: item.isChecked, isRequired: item.isMandatory, comment: item.comment, ); }).toList(); } void _onComplete() { // Validate mandatory checklist items final uncheckedMandatory = _taskData?.instructions?.where((item) => (item.isRequired ?? false) && !(item.isDone ?? false)).toList() ?? []; if (uncheckedMandatory.isNotEmpty) { _showErrorDialog(context, "Please complete all mandatory tasks (marked with *)"); return; } // Validate that at least one image is captured final capturedImages = _taskData?.imageRooms ?? []; print('DEBUG: Captured images count: ${capturedImages.length}'); print('DEBUG: Images: $capturedImages'); if (capturedImages.isEmpty) { _showErrorDialog(context, "Please capture at least one image before completing"); return; } // Calculate working hours double totalWorkingHours = 0; if (_timerStart != null) { final duration = DateTime.now().difference(_timerStart!); totalWorkingHours = duration.inSeconds / 3600.0; } // Navigate to preview screen Navigator.of(context).push( MaterialPageRoute( builder: (context) => OneTimeTaskPreviewScreen( taskData: _taskData!, totalWorkingHours: totalWorkingHours, onConfirm: () { Navigator.pop(context); // Close preview screen _performComplete(); // Proceed with completion }, ), ), ); } Future _performComplete() async { showDialog( context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading(), ); try { final instructionsPayload = _taskData?.instructions?.map((instruction) { return { 'id': instruction.id, 'isDone': instruction.isDone, 'comment': instruction.comment, }; }).toList() ?? []; // Convert images to attachments format final attachmentsPayload = _taskData?.imageRooms?.map((imageString) { // imageString format: "filename|base64data" final parts = imageString.split('|'); final originalName = parts.isNotEmpty ? parts[0] : 'image.jpg'; final base64Data = parts.length > 1 ? parts[1] : imageString; return { 'id': 0, 'name': "$originalName|$base64Data", // Send full formatted string 'originalName': originalName, }; }).toList() ?? []; final result = await CleanerProvider().completeOneTimeTask( id: _taskData!.id!, instructions: instructionsPayload, attachments: attachmentsPayload, ); if (mounted) Navigator.pop(context); // Close loading dialog if (result['success']) { if (mounted) { // Navigate to dashboard Navigator.of(context).pushNamedAndRemoveUntil( LandPage.routeName, (routes) => false, ); } } else { if (mounted) { _showErrorDialog(context, result['message'] ?? 'Failed to complete task'); } } } catch (e) { if (mounted) Navigator.pop(context); if (mounted) { _showErrorDialog(context, 'Failed to complete task. Please try again.'); } } } void _showErrorDialog(BuildContext context, String message) { showDialog( context: context, builder: (context) => ConfirmDialog( message: message, title: 'Error', onTap: () => Navigator.pop(context), ), ); } @override void dispose() { // Dispose all checklist item controllers if (_checklistItems != null) { for (var item in _checklistItems!) { item.dispose(); } } super.dispose(); } }