From 5c46fdd8e5e50d5ec6c1fca0040aba0ce438cbd2 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Thu, 21 May 2026 15:24:33 +0300 Subject: [PATCH] new flow api implemented --- lib/controllers/api_routes/urls.dart | 8 +- .../models/one_time_task_data.dart | 236 +++++++ .../models/qr_scan_location_data.dart | 60 ++ .../cleaner_module/models/task_type.dart | 26 + .../pages/cleaner_search_form_view.dart | 625 +++++++----------- .../pages/one_time_task_view.dart | 437 ++++++++++++ .../provider/cleaner_provider.dart | 179 +++++ .../provider/task_type_provider.dart | 74 +++ 8 files changed, 1243 insertions(+), 402 deletions(-) create mode 100644 lib/modules/cleaner_module/models/one_time_task_data.dart create mode 100644 lib/modules/cleaner_module/models/qr_scan_location_data.dart create mode 100644 lib/modules/cleaner_module/models/task_type.dart create mode 100644 lib/modules/cleaner_module/pages/one_time_task_view.dart create mode 100644 lib/modules/cleaner_module/provider/task_type_provider.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 2ba006ab..e2b02926 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -71,8 +71,12 @@ class URLs { static get searchPlanRecurrentTaskListSearch => "$_baseUrl/PlanRecurrentTasks/PlanRecurrentTaskListSearchDto"; // get static get checkQRSameRoom => "$_baseUrl/PlanRecurrentTasks/CheckQRSameRoom"; // get - // - + //One Time Task Urls cleaner module new flow urls .... + static get getTaskType => "$_baseUrl/OneTimeTasks/GetTaskTypes"; + static get scanQRCode => "$_baseUrl/OneTimeTasks/ScanQRCode"; + static get proceedRequest => "$_baseUrl/OneTimeTasks/Proceed"; + static get completeRequest => "$_baseUrl/OneTimeTasks/Complete"; + static get getRequestById => "$_baseUrl/OneTimeTasks/GetById"; //service request new flow urls. static get nurseDashboardCountUrl => '$_baseUrl/ServiceRequest/GetDashboardNurseCount'; diff --git a/lib/modules/cleaner_module/models/one_time_task_data.dart b/lib/modules/cleaner_module/models/one_time_task_data.dart new file mode 100644 index 00000000..817ae685 --- /dev/null +++ b/lib/modules/cleaner_module/models/one_time_task_data.dart @@ -0,0 +1,236 @@ +class OneTimeTaskData { + int? id; + int? oneTimeSettingId; + OneTimeSetting? oneTimeSetting; + String? userId; + String? userName; + int? siteId; + String? siteName; + int? buildingId; + String? buildingName; + int? floorId; + String? floorName; + int? departmentID; + String? departmentName; + int? roomId; + String? roomName; + String? locationDescription; + String? startTime; + String? endTime; + int? statusId; + String? statusName; + int? statusValue; + List? instructions; + List? attachments; + String? createdDate; + String? createdBy; + String? imageRoom; // For captured image + + OneTimeTaskData({ + this.id, + this.oneTimeSettingId, + this.oneTimeSetting, + this.userId, + this.userName, + this.siteId, + this.siteName, + this.buildingId, + this.buildingName, + this.floorId, + this.floorName, + this.departmentID, + this.departmentName, + this.roomId, + this.roomName, + this.locationDescription, + this.startTime, + this.endTime, + this.statusId, + this.statusName, + this.statusValue, + this.instructions, + this.attachments, + this.createdDate, + this.createdBy, + this.imageRoom, + }); + + factory OneTimeTaskData.fromJson(Map json) { + return OneTimeTaskData( + id: json['id'], + oneTimeSettingId: json['oneTimeSettingId'], + oneTimeSetting: json['oneTimeSetting'] != null ? OneTimeSetting.fromJson(json['oneTimeSetting']) : null, + userId: json['userId'], + userName: json['userName'], + siteId: json['siteId'], + siteName: json['siteName'], + buildingId: json['buildingId'], + buildingName: json['buildingName'], + floorId: json['floorId'], + floorName: json['floorName'], + departmentID: json['departmentID'], + departmentName: json['departmentName'], + roomId: json['roomId'], + roomName: json['roomName'], + locationDescription: json['locationDescription'], + startTime: json['startTime'], + endTime: json['endTime'], + statusId: json['statusId'], + statusName: json['statusName'], + statusValue: json['statusValue'], + instructions: json['instructions'] != null + ? (json['instructions'] as List).map((i) => Instruction.fromJson(i)).toList() + : null, + attachments: json['attachments'] != null + ? (json['attachments'] as List).map((a) => Attachment.fromJson(a)).toList() + : null, + createdDate: json['createdDate'], + createdBy: json['createdBy'], + imageRoom: json['imageRoom'], + ); + } + + Map toJson() { + return { + 'id': id, + 'oneTimeSettingId': oneTimeSettingId, + 'oneTimeSetting': oneTimeSetting?.toJson(), + 'userId': userId, + 'userName': userName, + 'siteId': siteId, + 'siteName': siteName, + 'buildingId': buildingId, + 'buildingName': buildingName, + 'floorId': floorId, + 'floorName': floorName, + 'departmentID': departmentID, + 'departmentName': departmentName, + 'roomId': roomId, + 'roomName': roomName, + 'locationDescription': locationDescription, + 'startTime': startTime, + 'endTime': endTime, + 'statusId': statusId, + 'statusName': statusName, + 'statusValue': statusValue, + 'instructions': instructions?.map((i) => i.toJson()).toList(), + 'attachments': attachments?.map((a) => a.toJson()).toList(), + 'createdDate': createdDate, + 'createdBy': createdBy, + 'imageRoom': imageRoom, + }; + } +} + +class OneTimeSetting { + int? id; + int? taskTypeId; + String? taskTypeName; + int? instructionDescriptionId; + String? instructionDescriptionName; + int? sla; + + OneTimeSetting({ + this.id, + this.taskTypeId, + this.taskTypeName, + this.instructionDescriptionId, + this.instructionDescriptionName, + this.sla, + }); + + factory OneTimeSetting.fromJson(Map json) { + return OneTimeSetting( + id: json['id'], + taskTypeId: json['taskTypeId'], + taskTypeName: json['taskTypeName'], + instructionDescriptionId: json['instructionDescriptionId'], + instructionDescriptionName: json['instructionDescriptionName'], + sla: json['sla'], + ); + } + + Map toJson() { + return { + 'id': id, + 'taskTypeId': taskTypeId, + 'taskTypeName': taskTypeName, + 'instructionDescriptionId': instructionDescriptionId, + 'instructionDescriptionName': instructionDescriptionName, + 'sla': sla, + }; + } +} + +class Instruction { + int? id; + int? instructionTextId; + String? instructionText; + bool? isRequired; + bool? isDone; + String? comment; + + Instruction({ + this.id, + this.instructionTextId, + this.instructionText, + this.isRequired, + this.isDone, + this.comment, + }); + + factory Instruction.fromJson(Map json) { + return Instruction( + id: json['id'], + instructionTextId: json['instructionTextId'], + instructionText: json['instructionText'], + isRequired: json['isRequired'], + isDone: json['isDone'], + comment: json['comment'], + ); + } + + Map toJson() { + return { + 'id': id, + 'instructionTextId': instructionTextId, + 'instructionText': instructionText, + 'isRequired': isRequired, + 'isDone': isDone, + 'comment': comment, + }; + } +} + +class Attachment { + int? id; + String? fileName; + String? fileUrl; + String? fileType; + + Attachment({ + this.id, + this.fileName, + this.fileUrl, + this.fileType, + }); + + factory Attachment.fromJson(Map json) { + return Attachment( + id: json['id'], + fileName: json['fileName'], + fileUrl: json['fileUrl'], + fileType: json['fileType'], + ); + } + + Map toJson() { + return { + 'id': id, + 'fileName': fileName, + 'fileUrl': fileUrl, + 'fileType': fileType, + }; + } +} + diff --git a/lib/modules/cleaner_module/models/qr_scan_location_data.dart b/lib/modules/cleaner_module/models/qr_scan_location_data.dart new file mode 100644 index 00000000..a5b914d0 --- /dev/null +++ b/lib/modules/cleaner_module/models/qr_scan_location_data.dart @@ -0,0 +1,60 @@ +class QrScanLocationData { + int? siteId; + String? siteName; + int? buildingId; + String? buildingName; + int? floorId; + String? floorName; + int? departmentId; + String? departmentName; + int? roomId; + String? roomName; + String? locationDescription; + + QrScanLocationData({ + this.siteId, + this.siteName, + this.buildingId, + this.buildingName, + this.floorId, + this.floorName, + this.departmentId, + this.departmentName, + this.roomId, + this.roomName, + this.locationDescription, + }); + + factory QrScanLocationData.fromJson(Map json) { + return QrScanLocationData( + siteId: json['siteId'], + siteName: json['siteName'], + buildingId: json['buildingId'], + buildingName: json['buildingName'], + floorId: json['floorId'], + floorName: json['floorName'], + departmentId: json['departmentId'], + departmentName: json['departmentName'], + roomId: json['roomId'], + roomName: json['roomName'], + locationDescription: json['locationDescription'], + ); + } + + Map toJson() { + return { + 'siteId': siteId, + 'siteName': siteName, + 'buildingId': buildingId, + 'buildingName': buildingName, + 'floorId': floorId, + 'floorName': floorName, + 'departmentId': departmentId, + 'departmentName': departmentName, + 'roomId': roomId, + 'roomName': roomName, + 'locationDescription': locationDescription, + }; + } +} + diff --git a/lib/modules/cleaner_module/models/task_type.dart b/lib/modules/cleaner_module/models/task_type.dart new file mode 100644 index 00000000..a42b906b --- /dev/null +++ b/lib/modules/cleaner_module/models/task_type.dart @@ -0,0 +1,26 @@ +import 'package:test_sa/models/base.dart'; + +class TaskType extends Base { + int? id; + String? taskTypeName; + + TaskType({this.id, this.taskTypeName}) : super(identifier: id?.toString(), name: taskTypeName); + + @override + bool operator ==(Object other) => identical(this, other) || other is TaskType && id != null && id == other.id; + + @override + int get hashCode => id.hashCode; + + Map toJson() { + return {"id": id, "taskTypeName": taskTypeName}; + } + + factory TaskType.fromJson(Map? parsedJson) { + if (parsedJson == null) return TaskType(); + return TaskType( + id: parsedJson["id"], + taskTypeName: parsedJson["taskTypeName"], + ); + } +} diff --git a/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart b/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart index 2ed65884..9cc1f1d8 100644 --- a/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart +++ b/lib/modules/cleaner_module/pages/cleaner_search_form_view.dart @@ -1,27 +1,21 @@ import 'package:flutter/material.dart'; -import 'package:test_sa/controllers/providers/api/hospitals_provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; -import 'package:test_sa/extensions/string_extensions.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/models/hospital.dart'; -import 'package:test_sa/models/lookup.dart'; -import 'package:test_sa/models/new_models/department_room_combined.dart'; -import 'package:test_sa/modules/cleaner_module/pages/cleaner_search_list_view.dart'; +import 'package:test_sa/modules/cleaner_module/models/task_type.dart'; +import 'package:test_sa/modules/cleaner_module/models/qr_scan_location_data.dart'; import 'package:test_sa/modules/cleaner_module/provider/cleaner_provider.dart'; -import 'package:test_sa/modules/cleaner_module/provider/fms_building_provider.dart'; -import 'package:test_sa/modules/cleaner_module/provider/fms_floor_provider.dart'; -import 'package:test_sa/modules/cleaner_module/provider/fms_location_description_provider.dart'; +import 'package:test_sa/modules/cleaner_module/provider/task_type_provider.dart'; +import 'package:test_sa/modules/cleaner_module/pages/one_time_task_view.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/providers/loading_list_notifier.dart'; -import 'package:test_sa/models/new_models/building.dart'; -import 'package:test_sa/models/new_models/floor.dart'; -import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; +import 'package:test_sa/views/widgets/qr/scan_qr.dart'; class CleanerSearchFormView extends StatefulWidget { static const String routeName = '/cleaner_search'; @@ -33,165 +27,140 @@ class CleanerSearchFormView extends StatefulWidget { } class _CleanerSearchFormViewState extends State { - Hospital? selectedSite; - Building? selectedBuilding; - Floor? selectedFloor; - DepartmentRoomCombined? selectedDepartmentRoom; - Lookup? selectedLocationDescription; - late FocusNode _siteFocusNode; - final TextEditingController _siteController = TextEditingController(); - - List _siteOptions = []; - List _buildingOptions = []; - List _floorOptions = []; - List _locationDescriptionOptions = []; - List _allDeptRoomList = []; - - bool _isLoadingSites = false; - bool _isLoadingBuildings = false; - bool _isLoadingFloors = false; - bool _isLoadingLocationDescriptions = false; - bool _isSearching = false; + TaskType? selectedTaskType; + List _taskTypeOptions = []; + bool _isLoadingTaskTypes = false; + bool _isScanning = false; + bool _isProceeding = false; + QrScanLocationData? _scannedLocationData; @override void initState() { super.initState(); - _siteFocusNode = FocusNode(); + _loadTaskTypes(); } @override void dispose() { - _siteController.dispose(); - _siteFocusNode.dispose(); super.dispose(); } - Future _searchSites(String query) async { - if (query.isEmpty) { - setState(() { - _siteOptions = []; - }); - return; - } - + Future _loadTaskTypes() async { setState(() { - _isLoadingSites = true; + _isLoadingTaskTypes = true; }); try { - final sites = await HospitalsProvider().getHospitalsListByVal(searchVal: query); + final taskTypes = await TaskTypeProvider().getTaskTypesList(); setState(() { - _siteOptions = sites; - _isLoadingSites = false; + _taskTypeOptions = taskTypes; + _isLoadingTaskTypes = false; }); } catch (e) { setState(() { - _isLoadingSites = false; + _isLoadingTaskTypes = false; + _taskTypeOptions = []; }); - _showErrorDialog(context, 'Failed to load sites. Please try again.'); + _showErrorDialog(context, 'Failed to load task types. Please try again.'); } } - Future _loadBuildings(int siteId) async { - setState(() { - _isLoadingBuildings = true; - }); + Future _onScanQRCode() async { + final result = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => const ScanQr()), + ); - try { - final buildings = await FmsBuildingProvider().getBuildingsListBySiteId(siteId); - setState(() { - _buildingOptions = buildings; - _isLoadingBuildings = false; - }); - } catch (e) { + if (result != null && result is String && result.isNotEmpty) { setState(() { - _isLoadingBuildings = false; - _buildingOptions = []; + _isScanning = true; }); - _showErrorDialog(context, 'Failed to load buildings. Please try again.'); + + try { + final qrResult = await CleanerProvider().scanQRCode(qrCode: result); + + final isSuccess = qrResult["success"] ?? false; + final backendMessage = qrResult["message"] ?? "Unknown error occurred"; + + if (isSuccess && qrResult["data"] != null) { + // Store the scanned location data + setState(() { + _scannedLocationData = QrScanLocationData.fromJson(qrResult["data"]); + _isScanning = false; + }); + } else { + setState(() { + _isScanning = false; + }); + if (mounted) { + _showErrorDialog(context, backendMessage); + } + } + } catch (e) { + setState(() { + _isScanning = false; + }); + if (mounted) { + _showErrorDialog(context, 'Failed to scan QR code. Please try again.'); + } + } } } - Future _loadFloors(int siteId, int buildingId) async { - setState(() { - _isLoadingFloors = true; - }); + Future _onProceed() async { + // Validate that task type and location data are available + if (selectedTaskType == null) { + _showErrorDialog(context, 'Please select a task type'); + return; + } - try { - final floors = await FmsFloorProvider().getFloorsListBySiteAndBuilding(siteId, buildingId); - setState(() { - _floorOptions = floors; - _isLoadingFloors = false; - }); - } catch (e) { - setState(() { - _isLoadingFloors = false; - _floorOptions = []; - }); - _showErrorDialog(context, 'Failed to load floors. Please try again.'); + if (_scannedLocationData == null) { + _showErrorDialog(context, 'Please scan a QR code first'); + return; } - } - Future _loadLocationDescriptions(int siteId, int buildingId, int floorId) async { setState(() { - _isLoadingLocationDescriptions = true; + _isProceeding = true; }); try { - final response = await FmsLocationDescriptionProvider().getLocationDescriptionsList(siteId, buildingId, floorId); - setState(() { - _locationDescriptionOptions = response?.locationDescriptions ?? []; - _allDeptRoomList = response?.deptRoom ?? []; - _isLoadingLocationDescriptions = false; - }); - } catch (e) { - setState(() { - _isLoadingLocationDescriptions = false; - _locationDescriptionOptions = []; - _allDeptRoomList = []; - }); - _showErrorDialog(context, 'Failed to load location descriptions. Please try again.'); - } - } - - List _getDepartmentRoomCombinations() { - if (selectedLocationDescription == null || _allDeptRoomList.isEmpty) return []; - - // Filter dept room list by selected location description - List filteredList = _allDeptRoomList.where((item) => item.locationDescriptionName == selectedLocationDescription!.name).toList(); - - // Convert to DepartmentRoomCombined - return filteredList - .map((item) => DepartmentRoomCombined( - deptId: item.deptId, - roomId: item.roomId, - deptName: item.deptName, - roomName: item.roomName, - locationDescriptionName: item.locationDescriptionName, - deptAndRoomName: item.deptAndRoomName, - )) - .toList(); - } + final result = await CleanerProvider().proceedOneTimeTask( + oneTimeSettingId: selectedTaskType!.id ?? 0, + siteId: _scannedLocationData!.siteId ?? 0, + buildingId: _scannedLocationData!.buildingId ?? 0, + floorId: _scannedLocationData!.floorId ?? 0, + departmentId: _scannedLocationData!.departmentId ?? 0, + roomId: _scannedLocationData!.roomId ?? 0, + ); - void _autoSelectDepartmentRoom() { - final combinations = _getDepartmentRoomCombinations(); + final isSuccess = result["success"] ?? false; + final data = result["data"]; + final message = result["message"] ?? "Unknown error occurred"; - if (combinations.isEmpty) { - setState(() { - selectedDepartmentRoom = null; - }); - return; - } + if (isSuccess && data != null) { + // Get the task ID from the response + final taskId = data['id']; - // If only one item, auto-fill - if (combinations.length == 1) { - setState(() { - selectedDepartmentRoom = combinations.first; - }); - } else { - // If more than one, auto-select first item + if (mounted && taskId != null) { + // Navigate to OneTimeTaskView + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => OneTimeTaskView(taskId: taskId), + ), + ); + } + } else { + if (mounted) { + _showErrorDialog(context, message); + } + } + } catch (e) { + if (mounted) { + _showErrorDialog(context, 'Failed to proceed. Please try again.'); + } + } finally { setState(() { - selectedDepartmentRoom = combinations.first; + _isProceeding = false; }); } } @@ -213,253 +182,92 @@ class _CleanerSearchFormViewState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Site Autocomplete Field - Container( - padding: EdgeInsets.symmetric( - horizontal: 16.toScreenWidth, - vertical: 8.toScreenHeight, - ), - decoration: BoxDecoration( - color: AppColor.fieldBgColor(context), - borderRadius: BorderRadius.circular(10), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Site', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.neutral20, - fontWeight: FontWeight.w500, - ), - ), - SizedBox(height: 4.toScreenHeight), - SizedBox( - height: 24.toScreenHeight, // Fixed height for consistency - child: Autocomplete( - optionsBuilder: (TextEditingValue textEditingValue) async { - if (textEditingValue.text.isEmpty) { - return const Iterable.empty(); - } - await _searchSites(textEditingValue.text); - return _siteOptions; - }, - displayStringForOption: (Hospital option) => option.name ?? "", - onSelected: (Hospital selected) async { - // _siteFocusNode.unfocus(); - FocusManager.instance.primaryFocus?.unfocus(); - setState(() { - selectedSite = selected; - _siteController.text = selected.name ?? ''; - // Reset dependent dropdowns - selectedBuilding = null; - selectedFloor = null; - selectedLocationDescription = null; - selectedDepartmentRoom = null; - _buildingOptions = []; - _floorOptions = []; - _locationDescriptionOptions = []; - _allDeptRoomList = []; - }); - - // Load buildings for selected site - if (selected.id != null) { - await _loadBuildings(selected.id!); - } - }, - fieldViewBuilder: (context, controller, focusNode, onFieldSubmitted) { - if (_siteController.text.isNotEmpty && controller.text.isEmpty) { - controller.text = _siteController.text; - } - - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: TextField( - controller: controller, - // focusNode: _siteFocusNode, - focusNode: focusNode, - style: AppTextStyles.bodyText.copyWith( - color: context.isDark ? AppColor.neutral30 : AppColor.black10, - ), - decoration: InputDecoration( - hintText: 'Search site...', - hintStyle: AppTextStyles.bodyText.copyWith( - color: AppColor.neutral20, - ), - filled: false, - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - contentPadding: EdgeInsets.zero, - isDense: true, - ), - onChanged: (value) { - _siteController.text = value; - }, - ), - ), - if (_isLoadingSites) - const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - color: AppColor.primary10, - ), - ) - else if (controller.text.isNotEmpty) - GestureDetector( - onTap: () { - controller.clear(); - _siteController.clear(); - setState(() { - selectedSite = null; - selectedBuilding = null; - selectedFloor = null; - selectedLocationDescription = null; - selectedDepartmentRoom = null; - _siteOptions = []; - _buildingOptions = []; - _floorOptions = []; - _locationDescriptionOptions = []; - _allDeptRoomList = []; - }); - }, - child: const Icon( - Icons.clear, - color: AppColor.neutral20, - size: 20, - ), - ), - ], - ); - }, - ), - ), - ], - ), - ), - const SizedBox(height: 18), - - // Building Dropdown - SingleItemDropDownMenu( + // Task Type Dropdown + SingleItemDropDownMenu( context: context, - title: 'Building', - initialValue: selectedBuilding, + title: 'Task Type', + initialValue: selectedTaskType, showShadow: false, showAsBottomSheet: true, backgroundColor: AppColor.fieldBgColor(context), - enabled: _buildingOptions.isNotEmpty, - loading: _isLoadingBuildings, - staticData: _buildingOptions, - onSelect: (value) async { - _siteFocusNode.unfocus(); - + enabled: _taskTypeOptions.isNotEmpty, + loading: _isLoadingTaskTypes, + staticData: _taskTypeOptions, + onSelect: (value) { setState(() { - _siteOptions = []; - print('site options length ${_siteOptions.length}'); - selectedBuilding = value; - selectedFloor = null; - selectedLocationDescription = null; - selectedDepartmentRoom = null; - _floorOptions = []; - _locationDescriptionOptions = []; - _allDeptRoomList = []; + selectedTaskType = value; }); - - // Load floors for selected building - if (value != null && selectedSite?.id != null && value.id != null) { - await _loadFloors(selectedSite!.id!, value.id!.toInt()); - } }, ), const SizedBox(height: 18), - // Floor Dropdown - SingleItemDropDownMenu( - context: context, - title: 'Floor', - initialValue: selectedFloor, - showShadow: false, - showAsBottomSheet: true, - backgroundColor: AppColor.fieldBgColor(context), - enabled: _floorOptions.isNotEmpty, - loading: _isLoadingFloors, - staticData: _floorOptions, - onSelect: (value) async { - setState(() { - selectedFloor = value; - selectedLocationDescription = null; - selectedDepartmentRoom = null; - _locationDescriptionOptions = []; - _allDeptRoomList = []; - }); - - // Load location descriptions for selected floor - if (value != null && selectedSite?.id != null && selectedBuilding?.id != null && value.id != null) { - await _loadLocationDescriptions( - selectedSite!.id!, - selectedBuilding!.id!.toInt(), - value.id!.toInt(), - ); - } - }, + // Scan QR Code Button + GestureDetector( + onTap: selectedTaskType != null ? _onScanQRCode : null, + child: Container( + height: 50, + alignment: Alignment.center, + decoration: BoxDecoration( + color: selectedTaskType != null ? Colors.white : Colors.grey[300], + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: selectedTaskType != null ? AppColor.blueStatus(context) : Colors.grey, + width: 2, + ), + ), + padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth, vertical: 8.toScreenHeight), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + "scan_asset".toSvgAsset( + height: 22, + fit: BoxFit.fitHeight, + color: selectedTaskType != null + ? (context.isDark ? AppColor.neutral20 : AppColor.neutral120) + : Colors.grey, + ), + 8.width, + //we can change the implementation when merge with uiux branch + _scannedLocationData==null? "Scan QR Code".bodyText(context).custom( + color: selectedTaskType != null + ? (context.isDark ? AppColor.neutral20 : AppColor.black20) + : Colors.grey, + ):'Re Scan QR Code'.bodyText(context).custom( + color: selectedTaskType != null + ? (context.isDark ? AppColor.neutral20 : AppColor.black20) + : Colors.grey, + ), + ], + ), + ), ), const SizedBox(height: 18), - // Location Description Dropdown - SingleItemDropDownMenu( - context: context, - title: 'Location Description', - initialValue: selectedLocationDescription, - showShadow: false, - showAsBottomSheet: true, - backgroundColor: AppColor.fieldBgColor(context), - enabled: _locationDescriptionOptions.isNotEmpty, - loading: _isLoadingLocationDescriptions, - staticData: _locationDescriptionOptions, - onSelect: (value) { - setState(() { - selectedLocationDescription = value; - selectedDepartmentRoom = null; - }); - - // Auto-select department-room based on location description - _autoSelectDepartmentRoom(); - }, - ), - const SizedBox(height: 18), + // Loading Indicator + if (_isScanning) + const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + ), + ), - // Department & Room Combined Dropdown - SingleItemDropDownMenu( - context: context, - title: 'Department - Room', - initialValue: selectedDepartmentRoom, - showShadow: false, - showAsBottomSheet: true, - backgroundColor: AppColor.fieldBgColor(context), - enabled: _getDepartmentRoomCombinations().isNotEmpty, - staticData: _getDepartmentRoomCombinations(), - onSelect: (value) { - setState(() { - selectedDepartmentRoom = value; - }); - }, - ), + // Scanned Location Data Card + if (_scannedLocationData != null && !_isScanning) _buildLocationDataCard(context), ], ).toShadowContainer(context).paddingOnly(top: 20, start: 16, end: 16), ), ), + + // Footer Action Button FooterActionButton.footerContainer( context: context, child: AppFilledButton( - buttonColor: AppColor.primary10, - label: context.translation.search, + buttonColor:_scannedLocationData != null && !_isProceeding? AppColor.primary10: Colors.grey, + label: 'Proceed', maxWidth: true, - onPressed: _isSearching ? null : _onSubmit, - loading: _isSearching, + onPressed: (_scannedLocationData != null && !_isProceeding) ? _onProceed : null, + loading: _isProceeding, ), ), ], @@ -467,52 +275,69 @@ class _CleanerSearchFormViewState extends State { ); } - Future _onSubmit() async { - // Validate that at least site is selected - if (selectedSite == null) { - _showErrorDialog(context, 'Please select a site'); - return; - } + Widget _buildLocationDataCard(BuildContext context) { + final locationData = _scannedLocationData; - try { - setState(() { - _isSearching = true; - }); - - // Call the search API - final response = await CleanerProvider().searchPlanRecurrentTasksList( - pageSize: 10, - pageNumber: 1, - siteId: selectedSite?.id, - buildingId: selectedBuilding?.id?.toInt(), - floorId: selectedFloor?.id?.toInt(), - departmentId: selectedDepartmentRoom?.deptId, - roomId: selectedDepartmentRoom?.roomId, - ); - - final results = response['data'] as List; - final totalRows = response['totalRows'] as int; - - // Navigate to search results page with the results - Navigator.of(context).pushNamed( - CleanerSearchListView.routeName, - arguments: { - 'searchResults': results, - 'totalRows': totalRows, - 'selectedSite': selectedSite, - 'selectedBuilding': selectedBuilding, - 'selectedFloor': selectedFloor, - 'selectedLocationDescription': selectedLocationDescription, - 'selectedDepartmentRoom': selectedDepartmentRoom, - }, - ); - } catch (e) { - _showErrorDialog(context, 'Error searching. Please try again.'); - } finally { - setState(() { - _isSearching = false; - }); + if (locationData == 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: [ + if (locationData.siteName != null && locationData.siteName!.isNotEmpty) + Text( + 'Site: ${locationData.siteName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + if (locationData.buildingName != null && locationData.buildingName!.isNotEmpty) + Text( + 'Building: ${locationData.buildingName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + if (locationData.floorName != null && locationData.floorName!.isNotEmpty) + Text( + 'Floor: ${locationData.floorName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + if (locationData.departmentName != null && locationData.departmentName!.isNotEmpty) + Text( + 'Department: ${locationData.departmentName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + if (locationData.roomName != null && locationData.roomName!.isNotEmpty) + Text( + 'Room: ${locationData.roomName}', + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + ], + ), + ], + ), + ], + ).paddingOnly(start: 16, end: 16, top: 16, bottom: 16), + ], + ).toShadowContainer(context, padding: 0); } void _showErrorDialog(BuildContext context, String message) { diff --git a/lib/modules/cleaner_module/pages/one_time_task_view.dart b/lib/modules/cleaner_module/pages/one_time_task_view.dart new file mode 100644 index 00000000..d1037aa2 --- /dev/null +++ b/lib/modules/cleaner_module/pages/one_time_task_view.dart @@ -0,0 +1,437 @@ +import 'package:flutter/material.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/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/widgets/cleaner_preview_dialog.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'; + +class OneTimeTaskView extends StatefulWidget { + static const String routeName = "/one_time_task"; + final int taskId; + + const OneTimeTaskView({Key? key, required this.taskId}) : super(key: key); + + @override + State createState() => _OneTimeTaskViewState(); +} + +class _OneTimeTaskViewState extends State { + OneTimeTaskData? _taskData; + bool _isLoading = false; + DateTime? _timerStart; + + @override + void initState() { + super.initState(); + _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']); + _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', + onWillPopScope: isClosed + ? null + : () { + _showExitDialog(); + }, + ), + 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); + }); + }, + capturedImageBase64: _taskData!.imageRoom, + onImageCaptured: (imageBase64) { + setState(() { + _taskData!.imageRoom = imageBase64; + }); + }, + ), + ], + ), + ), + ), + if (!isClosed) ...[ + FooterActionButton.footerContainer( + context: context, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + AppFilledButton( + label: context.translation.save, + buttonColor: context.isDark ? AppColor.neutral20 : AppColor.white60, + textColor: context.isDark ? Colors.white : AppColor.black10, + onPressed: () => Navigator.pop(context), + ).expanded, + 12.width, + AppFilledButton( + label: context.translation.complete, + buttonColor: AppColor.primary10, + onPressed: _onComplete, + ).expanded, + ], + ), + ), + ] + ], + ), + ) + : NoDataFound(message: context.translation.noDataFound).center, + ).handlePopScope( + cxt: context, + showPopUp: !isClosed, + 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, + children: [ + if (_taskData?.statusName != null) + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: _getStatusColor().withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + _taskData!.statusName!, + style: AppTextStyles.bodyText.copyWith( + color: _getStatusColor(), + fontWeight: FontWeight.w600, + ), + ), + ), + if (_taskData?.createdDate != null) + Text( + _formatDate(_taskData!.createdDate!), + 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 + if (_timerStart != null && !isClosed) ...[ + 12.height, + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.primary10.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: AppColor.primary10), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Working Time:', + style: AppTextStyles.bodyText.copyWith( + fontWeight: FontWeight.w600, + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + ), + ), + Text( + _formatDuration(DateTime.now().difference(_timerStart!)), + style: AppTextStyles.heading4.copyWith( + color: AppColor.primary10, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ], + ).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() { + return _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() ?? []; + } + + 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 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; + } + + // Show preview dialog + _showPreviewDialog(); + } + + void _showPreviewDialog() { + // Calculate working hours + double totalWorkingHours = 0; + if (_timerStart != null) { + final duration = DateTime.now().difference(_timerStart!); + totalWorkingHours = duration.inSeconds / 3600.0; + } + + CleanerPreviewDialog.show( + context, + model: _convertToRecurrentWoData(), + totalWorkingHours: totalWorkingHours, + onConfirm: _performComplete, + ); + } + + // Convert OneTimeTaskData to RecurrentWoData for preview dialog compatibility + dynamic _convertToRecurrentWoData() { + return { + 'siteName': _taskData?.siteName, + 'buildingName': _taskData?.buildingName, + 'floorName': _taskData?.floorName, + 'departmentName': _taskData?.departmentName, + 'roomName': _taskData?.roomName, + 'locationDescription': _taskData?.locationDescription, + 'taskNo': 'OT-${_taskData?.id}', + 'title': _taskData?.oneTimeSetting?.taskTypeName, + }; + } + + Future _performComplete() async { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const AppLazyLoading(), + ); + + try { + final instructionsPayload = _taskData?.instructions?.map((instruction) { + return { + 'id': instruction.id, + 'instructionTextId': instruction.instructionTextId, + 'instructionText': instruction.instructionText, + 'isRequired': instruction.isRequired, + 'isDone': instruction.isDone, + 'comment': instruction.comment, + }; + }).toList() ?? []; + + final result = await CleanerProvider().completeOneTimeTask( + id: _taskData!.id!, + instructions: instructionsPayload, + imageRoom: _taskData?.imageRoom, + ); + + 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 _showExitDialog() { + showDialog( + context: context, + builder: (context) => ConfirmDialog( + message: 'Do you want to exit without completing?', + title: 'Confirm', + onTap: () { + Navigator.pop(context); // Close dialog + Navigator.pop(context); // Exit screen + }, + ), + ); + } + + void _showErrorDialog(BuildContext context, String message) { + showDialog( + context: context, + builder: (context) => ConfirmDialog( + message: message, + title: 'Error', + onTap: () => Navigator.pop(context), + ), + ); + } + + Color _getStatusColor() { + if (_taskData?.statusValue == 2) { + return Colors.green; + } + return AppColor.primary10; + } + + String _formatDate(String dateStr) { + try { + final date = DateTime.parse(dateStr); + return '${date.day}/${date.month}/${date.year}'; + } catch (e) { + return dateStr; + } + } + + String _formatDuration(Duration duration) { + final hours = duration.inHours; + final minutes = duration.inMinutes.remainder(60); + final seconds = duration.inSeconds.remainder(60); + return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; + } +} diff --git a/lib/modules/cleaner_module/provider/cleaner_provider.dart b/lib/modules/cleaner_module/provider/cleaner_provider.dart index ec6fff3f..18a442b3 100644 --- a/lib/modules/cleaner_module/provider/cleaner_provider.dart +++ b/lib/modules/cleaner_module/provider/cleaner_provider.dart @@ -102,6 +102,7 @@ class CleanerProvider extends ChangeNotifier { int? floorId, int? departmentId, int? roomId, + int? taskTypeId, }) async { Response response; try { @@ -115,6 +116,7 @@ class CleanerProvider extends ChangeNotifier { "floorId": floorId, "departmentId": departmentId, "roomId": roomId, + "taskTypeId": taskTypeId, }, ); @@ -203,6 +205,183 @@ class CleanerProvider extends ChangeNotifier { } } + /// Scan QR Code and get task details + /// Returns a map with success status, message, and data from backend + Future> scanQRCode({ + required String qrCode, + }) async { + Response response; + try { + response = await ApiManager.instance.post( + URLs.scanQRCode, + body: { + "qrCode": qrCode, + }, + showToast: false, // Disable automatic toast message + ); + + // Parse the response body to extract the message + final responseData = json.decode(response.body); + + if (response.statusCode >= 200 && response.statusCode < 300) { + // Success response + return { + "success": true, + "data": responseData["data"], + "message": responseData["message"] ?? "QR code scanned successfully", + }; + } else { + // Error response - extract the exact error message from backend + return { + "success": false, + "data": null, + "message": responseData["message"] ?? "QR code scan failed", + }; + } + } catch (error) { + debugPrint('Error scanning QR code: $error'); + return { + "success": false, + "data": null, + "message": "Failed to scan QR code. Please check your connection and try again.", + }; + } + } + + /// Proceed with OneTime Task request + /// Returns a map with success status, message, and data from backend + Future> proceedOneTimeTask({ + required int oneTimeSettingId, + required int siteId, + required int buildingId, + required int floorId, + required int departmentId, + required int roomId, + }) async { + Response response; + try { + response = await ApiManager.instance.post( + URLs.proceedRequest, + body: { + "oneTimeSettingId": oneTimeSettingId, + "siteId": siteId, + "buildingId": buildingId, + "floorId": floorId, + "departmentId": departmentId, + "roomId": roomId, + }, + showToast: false, // Disable automatic toast message + ); + + // Parse the response body to extract the message + final responseData = json.decode(response.body); + + if (response.statusCode >= 200 && response.statusCode < 300) { + // Success response + return { + "success": true, + "data": responseData["data"], + "message": responseData["message"] ?? "Request proceeded successfully", + }; + } else { + // Error response - extract the exact error message from backend + return { + "success": false, + "data": null, + "message": responseData["message"] ?? "Failed to proceed with request", + }; + } + } catch (error) { + debugPrint('Error proceeding with request: $error'); + return { + "success": false, + "data": null, + "message": "Failed to proceed with request. Please check your connection and try again.", + }; + } + } + + /// Get OneTime Task by ID using getRequestById endpoint + Future> getOneTimeTaskById({ + required int id, + }) async { + Response response; + try { + response = await ApiManager.instance.get( + '${URLs.getRequestById}/$id', + ); + + // Parse the response body + final responseData = json.decode(response.body); + + if (response.statusCode >= 200 && response.statusCode < 300) { + // Success response + return { + "success": true, + "data": responseData["data"], + "message": responseData["message"] ?? "Task retrieved successfully", + }; + } else { + // Error response + return { + "success": false, + "data": null, + "message": responseData["message"] ?? "Failed to retrieve task", + }; + } + } catch (error) { + debugPrint('Error getting one time task: $error'); + return { + "success": false, + "data": null, + "message": "Failed to retrieve task. Please check your connection and try again.", + }; + } + } + + /// Complete OneTime Task + Future> completeOneTimeTask({ + required int id, + required List> instructions, + String? imageRoom, + }) async { + Response response; + try { + response = await ApiManager.instance.post( + URLs.completeRequest, + body: { + "id": id, + "instructions": instructions, + "imageRoom": imageRoom, + }, + showToast: false, + ); + + final responseData = json.decode(response.body); + + if (response.statusCode >= 200 && response.statusCode < 300) { + return { + "success": true, + "data": responseData["data"], + "message": responseData["message"] ?? "Task completed successfully", + }; + } else { + return { + "success": false, + "data": null, + "message": responseData["message"] ?? "Failed to complete task", + }; + } + } catch (error) { + debugPrint('Error completing one time task: $error'); + return { + "success": false, + "data": null, + "message": "Failed to complete task. Please check your connection and try again.", + }; + } + } + void reset() { _isLoading = false; _searchResults = null; diff --git a/lib/modules/cleaner_module/provider/task_type_provider.dart b/lib/modules/cleaner_module/provider/task_type_provider.dart new file mode 100644 index 00000000..624e5c36 --- /dev/null +++ b/lib/modules/cleaner_module/provider/task_type_provider.dart @@ -0,0 +1,74 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/modules/cleaner_module/models/task_type.dart'; + +class TaskTypeProvider extends ChangeNotifier { + //reset provider data + void reset() { + _taskTypes = null; + _stateCode = null; + } + + // state code of current request to define error message + int? _stateCode; + int? get stateCode => _stateCode; + + // contain task types data + List? _taskTypes; + List? get taskTypes => _taskTypes; + + // loading state + bool _loading = false; + bool get isLoading => _loading; + + set isLoading(bool isLoading) { + _loading = isLoading; + notifyListeners(); + } + + /// Get task types + Future getTaskTypes() async { + if (_loading == true) return -2; + _loading = true; + notifyListeners(); + Response response; + try { + response = await ApiManager.instance.get(URLs.getTaskType); + _stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + List taskTypesListJson = json.decode(response.body)["data"]; + _taskTypes = taskTypesListJson.map((taskType) => TaskType.fromJson(taskType)).toList(); + } + _loading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + _loading = false; + _stateCode = -1; + notifyListeners(); + return -1; + } + } + + /// Get task types list directly without provider state + Future> getTaskTypesList() async { + Response response; + try { + response = await ApiManager.instance.get(URLs.getTaskType); + + if (response.statusCode >= 200 && response.statusCode < 300) { + List taskTypesListJson = json.decode(response.body)["data"]; + return taskTypesListJson.map((taskType) => TaskType.fromJson(taskType)).toList(); + } + return []; + } catch (error) { + return []; + } + } +} +