|
|
|
|
@ -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<CleanerSearchFormView> {
|
|
|
|
|
Hospital? selectedSite;
|
|
|
|
|
Building? selectedBuilding;
|
|
|
|
|
Floor? selectedFloor;
|
|
|
|
|
DepartmentRoomCombined? selectedDepartmentRoom;
|
|
|
|
|
Lookup? selectedLocationDescription;
|
|
|
|
|
late FocusNode _siteFocusNode;
|
|
|
|
|
final TextEditingController _siteController = TextEditingController();
|
|
|
|
|
|
|
|
|
|
List<Hospital> _siteOptions = [];
|
|
|
|
|
List<Building> _buildingOptions = [];
|
|
|
|
|
List<Floor> _floorOptions = [];
|
|
|
|
|
List<Lookup> _locationDescriptionOptions = [];
|
|
|
|
|
List<DepartmentRoomItem> _allDeptRoomList = [];
|
|
|
|
|
|
|
|
|
|
bool _isLoadingSites = false;
|
|
|
|
|
bool _isLoadingBuildings = false;
|
|
|
|
|
bool _isLoadingFloors = false;
|
|
|
|
|
bool _isLoadingLocationDescriptions = false;
|
|
|
|
|
bool _isSearching = false;
|
|
|
|
|
TaskType? selectedTaskType;
|
|
|
|
|
List<TaskType> _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<void> _searchSites(String query) async {
|
|
|
|
|
if (query.isEmpty) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_siteOptions = [];
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> _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<void> _loadBuildings(int siteId) async {
|
|
|
|
|
setState(() {
|
|
|
|
|
_isLoadingBuildings = true;
|
|
|
|
|
});
|
|
|
|
|
Future<void> _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<void> _loadFloors(int siteId, int buildingId) async {
|
|
|
|
|
setState(() {
|
|
|
|
|
_isLoadingFloors = true;
|
|
|
|
|
});
|
|
|
|
|
Future<void> _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<void> _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<DepartmentRoomCombined> _getDepartmentRoomCombinations() {
|
|
|
|
|
if (selectedLocationDescription == null || _allDeptRoomList.isEmpty) return [];
|
|
|
|
|
|
|
|
|
|
// Filter dept room list by selected location description
|
|
|
|
|
List<DepartmentRoomItem> 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<CleanerSearchFormView> {
|
|
|
|
|
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<Hospital>(
|
|
|
|
|
optionsBuilder: (TextEditingValue textEditingValue) async {
|
|
|
|
|
if (textEditingValue.text.isEmpty) {
|
|
|
|
|
return const Iterable<Hospital>.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<Building, NullableLoadingProvider>(
|
|
|
|
|
// Task Type Dropdown
|
|
|
|
|
SingleItemDropDownMenu<TaskType, NullableLoadingProvider>(
|
|
|
|
|
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<Floor, NullableLoadingProvider>(
|
|
|
|
|
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<Lookup, NullableLoadingProvider>(
|
|
|
|
|
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<DepartmentRoomCombined, NullableLoadingProvider>(
|
|
|
|
|
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<CleanerSearchFormView> {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> _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<dynamic>;
|
|
|
|
|
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) {
|
|
|
|
|
|