repair marine report log menu
This commit is contained in:
parent
da892821a2
commit
fa5f0361de
@ -106,6 +106,10 @@ import 'package:environment_monitoring_app/screens/marine/manual/reports/marine_
|
|||||||
as marineManualEquipmentMaintenance;
|
as marineManualEquipmentMaintenance;
|
||||||
import 'package:environment_monitoring_app/screens/marine/manual/marine_manual_data_status_log.dart'
|
import 'package:environment_monitoring_app/screens/marine/manual/marine_manual_data_status_log.dart'
|
||||||
as marineManualDataStatusLog;
|
as marineManualDataStatusLog;
|
||||||
|
// *** START: ADDED NEW IMPORT ***
|
||||||
|
import 'package:environment_monitoring_app/screens/marine/manual/marine_manual_report_status_log.dart'
|
||||||
|
as marineManualReportStatusLog;
|
||||||
|
// *** END: ADDED NEW IMPORT ***
|
||||||
import 'package:environment_monitoring_app/screens/marine/manual/marine_image_request.dart' as marineManualImageRequest;
|
import 'package:environment_monitoring_app/screens/marine/manual/marine_image_request.dart' as marineManualImageRequest;
|
||||||
import 'package:environment_monitoring_app/screens/marine/continuous/marine_continuous_info_centre_document.dart';
|
import 'package:environment_monitoring_app/screens/marine/continuous/marine_continuous_info_centre_document.dart';
|
||||||
import 'package:environment_monitoring_app/screens/marine/continuous/overview.dart' as marineContinuousOverview;
|
import 'package:environment_monitoring_app/screens/marine/continuous/overview.dart' as marineContinuousOverview;
|
||||||
@ -461,6 +465,10 @@ class _RootAppState extends State<RootApp> {
|
|||||||
'/marine/manual/report/maintenance': (context) =>
|
'/marine/manual/report/maintenance': (context) =>
|
||||||
const marineManualEquipmentMaintenance.MarineManualEquipmentMaintenanceScreen(),
|
const marineManualEquipmentMaintenance.MarineManualEquipmentMaintenanceScreen(),
|
||||||
'/marine/manual/image-request': (context) => const marineManualImageRequest.MarineImageRequestScreen(),
|
'/marine/manual/image-request': (context) => const marineManualImageRequest.MarineImageRequestScreen(),
|
||||||
|
// *** START: ADDED NEW ROUTE ***
|
||||||
|
'/marine/manual/report-log': (context) =>
|
||||||
|
const marineManualReportStatusLog.MarineManualReportStatusLog(),
|
||||||
|
// *** END: ADDED NEW ROUTE ***
|
||||||
|
|
||||||
// Marine Continuous
|
// Marine Continuous
|
||||||
'/marine/continuous/info': (context) => const MarineContinuousInfoCentreDocument(),
|
'/marine/continuous/info': (context) => const MarineContinuousInfoCentreDocument(),
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
|
// lib/models/marine_manual_equipment_maintenance_data.dart
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class MarineManualEquipmentMaintenanceData {
|
class MarineManualEquipmentMaintenanceData {
|
||||||
int? conductedByUserId;
|
int? conductedByUserId;
|
||||||
|
// --- START: ADDED FIELD ---
|
||||||
|
String? conductedByUserName;
|
||||||
|
// --- END: ADDED FIELD ---
|
||||||
String? maintenanceDate;
|
String? maintenanceDate;
|
||||||
String? lastMaintenanceDate;
|
String? lastMaintenanceDate;
|
||||||
String? scheduleMaintenance;
|
String? scheduleMaintenance;
|
||||||
@ -25,6 +30,12 @@ class MarineManualEquipmentMaintenanceData {
|
|||||||
String? vanDornNewSerial;
|
String? vanDornNewSerial;
|
||||||
Map<String, Map<String, String>> vanDornReplacements = {};
|
Map<String, Map<String, String>> vanDornReplacements = {};
|
||||||
|
|
||||||
|
// --- START: Added Fields ---
|
||||||
|
String? submissionStatus;
|
||||||
|
String? submissionMessage;
|
||||||
|
String? reportId;
|
||||||
|
// --- END: Added Fields ---
|
||||||
|
|
||||||
|
|
||||||
// Constructor to initialize maps
|
// Constructor to initialize maps
|
||||||
MarineManualEquipmentMaintenanceData() {
|
MarineManualEquipmentMaintenanceData() {
|
||||||
@ -65,6 +76,36 @@ class MarineManualEquipmentMaintenanceData {
|
|||||||
vanDornReplacements[item] = {'Last Date': '', 'New Date': ''});
|
vanDornReplacements[item] = {'Last Date': '', 'New Date': ''});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- START: ADDED METHOD ---
|
||||||
|
/// Creates a JSON object for offline database storage.
|
||||||
|
Map<String, dynamic> toDbJson() {
|
||||||
|
return {
|
||||||
|
'conductedByUserId': conductedByUserId,
|
||||||
|
'conductedByUserName': conductedByUserName,
|
||||||
|
'maintenanceDate': maintenanceDate,
|
||||||
|
'lastMaintenanceDate': lastMaintenanceDate,
|
||||||
|
'scheduleMaintenance': scheduleMaintenance,
|
||||||
|
'isReplacement': isReplacement,
|
||||||
|
'timeStart': timeStart,
|
||||||
|
'timeEnd': timeEnd,
|
||||||
|
'location': location,
|
||||||
|
'ysiSondeChecks': ysiSondeChecks,
|
||||||
|
'ysiSondeComments': ysiSondeComments,
|
||||||
|
'ysiSensorChecks': ysiSensorChecks,
|
||||||
|
'ysiSensorComments': ysiSensorComments,
|
||||||
|
'ysiReplacements': ysiReplacements,
|
||||||
|
'vanDornChecks': vanDornChecks,
|
||||||
|
'vanDornComments': vanDornComments,
|
||||||
|
'vanDornCurrentSerial': vanDornCurrentSerial,
|
||||||
|
'vanDornNewSerial': vanDornNewSerial,
|
||||||
|
'vanDornReplacements': vanDornReplacements,
|
||||||
|
'submissionStatus': submissionStatus,
|
||||||
|
'submissionMessage': submissionMessage,
|
||||||
|
'reportId': reportId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// --- END: ADDED METHOD ---
|
||||||
|
|
||||||
// MODIFIED: This method now builds the complex nested structure the PHP controller expects.
|
// MODIFIED: This method now builds the complex nested structure the PHP controller expects.
|
||||||
Map<String, dynamic> toApiFormData() {
|
Map<String, dynamic> toApiFormData() {
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
|
// lib/models/marine_manual_pre_departure_checklist_data.dart
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class MarineManualPreDepartureChecklistData {
|
class MarineManualPreDepartureChecklistData {
|
||||||
String? reporterName;
|
String? reporterName;
|
||||||
int? reporterUserId;
|
int? reporterUserId;
|
||||||
String? submissionDate;
|
String? submissionDate;
|
||||||
|
// --- START: ADDED FIELD ---
|
||||||
|
String? location;
|
||||||
|
// --- END: ADDED FIELD ---
|
||||||
|
|
||||||
// Key: Item description, Value: true if 'Yes', false if 'No'
|
// Key: Item description, Value: true if 'Yes', false if 'No'
|
||||||
Map<String, bool> checklistItems = {};
|
Map<String, bool> checklistItems = {};
|
||||||
@ -11,8 +16,31 @@ class MarineManualPreDepartureChecklistData {
|
|||||||
// Key: Item description, Value: Remarks text
|
// Key: Item description, Value: Remarks text
|
||||||
Map<String, String> remarks = {};
|
Map<String, String> remarks = {};
|
||||||
|
|
||||||
|
// --- START: Added Fields ---
|
||||||
|
String? submissionStatus;
|
||||||
|
String? submissionMessage;
|
||||||
|
String? reportId;
|
||||||
|
// --- END: Added Fields ---
|
||||||
|
|
||||||
MarineManualPreDepartureChecklistData();
|
MarineManualPreDepartureChecklistData();
|
||||||
|
|
||||||
|
// --- START: ADDED METHOD ---
|
||||||
|
/// Creates a JSON object for offline database storage.
|
||||||
|
Map<String, dynamic> toDbJson() {
|
||||||
|
return {
|
||||||
|
'reporterName': reporterName,
|
||||||
|
'reporterUserId': reporterUserId,
|
||||||
|
'submissionDate': submissionDate,
|
||||||
|
'location': location, // <-- ADDED
|
||||||
|
'checklistItems': checklistItems,
|
||||||
|
'remarks': remarks,
|
||||||
|
'submissionStatus': submissionStatus,
|
||||||
|
'submissionMessage': submissionMessage,
|
||||||
|
'reportId': reportId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// --- END: ADDED METHOD ---
|
||||||
|
|
||||||
// MODIFIED: This method now builds the nested array structure the PHP controller expects.
|
// MODIFIED: This method now builds the nested array structure the PHP controller expects.
|
||||||
Map<String, dynamic> toApiFormData() {
|
Map<String, dynamic> toApiFormData() {
|
||||||
|
|
||||||
@ -31,6 +59,9 @@ class MarineManualPreDepartureChecklistData {
|
|||||||
return {
|
return {
|
||||||
'reporter_user_id': reporterUserId.toString(), // The controller gets this from auth, but good to send.
|
'reporter_user_id': reporterUserId.toString(), // The controller gets this from auth, but good to send.
|
||||||
'submission_date': submissionDate,
|
'submission_date': submissionDate,
|
||||||
|
// Note: 'location' is not sent to the API in this method,
|
||||||
|
// but it will be saved in the local log via toDbJson().
|
||||||
|
// If the API needs it, it must be added here.
|
||||||
'items': itemsList, // Send the formatted list
|
'items': itemsList, // Send the formatted list
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
|
// lib/models/marine_manual_sonde_calibration_data.dart
|
||||||
|
|
||||||
class MarineManualSondeCalibrationData {
|
class MarineManualSondeCalibrationData {
|
||||||
int? calibratedByUserId;
|
int? calibratedByUserId;
|
||||||
|
// --- START: ADDED FIELD ---
|
||||||
|
String? calibratedByUserName;
|
||||||
|
// --- END: ADDED FIELD ---
|
||||||
|
|
||||||
// Header fields from PDF
|
// Header fields from PDF
|
||||||
String? sondeSerialNumber;
|
String? sondeSerialNumber;
|
||||||
@ -30,6 +35,12 @@ class MarineManualSondeCalibrationData {
|
|||||||
String? calibrationStatus;
|
String? calibrationStatus;
|
||||||
String? remarks; // Matches "COMMENT/OBSERVATION"
|
String? remarks; // Matches "COMMENT/OBSERVATION"
|
||||||
|
|
||||||
|
// --- START: Added Fields ---
|
||||||
|
String? submissionStatus;
|
||||||
|
String? submissionMessage;
|
||||||
|
String? reportId;
|
||||||
|
// --- END: Added Fields ---
|
||||||
|
|
||||||
Map<String, dynamic> toApiFormData() {
|
Map<String, dynamic> toApiFormData() {
|
||||||
// This flat structure matches MarineSondeCalibrationController.php
|
// This flat structure matches MarineSondeCalibrationController.php
|
||||||
return {
|
return {
|
||||||
@ -58,4 +69,39 @@ class MarineManualSondeCalibrationData {
|
|||||||
'remarks': remarks,
|
'remarks': remarks,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- START: ADDED toDbJson METHOD ---
|
||||||
|
/// Creates a JSON object for offline database storage.
|
||||||
|
Map<String, dynamic> toDbJson() {
|
||||||
|
return {
|
||||||
|
'calibratedByUserId': calibratedByUserId,
|
||||||
|
'calibratedByUserName': calibratedByUserName, // <-- ADDED
|
||||||
|
'sondeSerialNumber': sondeSerialNumber,
|
||||||
|
'firmwareVersion': firmwareVersion,
|
||||||
|
'korVersion': korVersion,
|
||||||
|
'location': location,
|
||||||
|
'startDateTime': startDateTime,
|
||||||
|
'endDateTime': endDateTime,
|
||||||
|
'ph_7_mv': ph7Mv,
|
||||||
|
'ph_7_before': ph7Before,
|
||||||
|
'ph_7_after': ph7After,
|
||||||
|
'ph_10_mv': ph10Mv,
|
||||||
|
'ph_10_before': ph10Before,
|
||||||
|
'ph_10_after': ph10After,
|
||||||
|
'cond_before': condBefore,
|
||||||
|
'cond_after': condAfter,
|
||||||
|
'do_before': doBefore,
|
||||||
|
'do_after': doAfter,
|
||||||
|
'turbidity_0_before': turbidity0Before,
|
||||||
|
'turbidity_0_after': turbidity0After,
|
||||||
|
'turbidity_124_before': turbidity124Before,
|
||||||
|
'turbidity_124_after': turbidity124After,
|
||||||
|
'calibration_status': calibrationStatus,
|
||||||
|
'remarks': remarks,
|
||||||
|
'submissionStatus': submissionStatus,
|
||||||
|
'submissionMessage': submissionMessage,
|
||||||
|
'reportId': reportId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// --- END: ADDED toDbJson METHOD ---
|
||||||
}
|
}
|
||||||
@ -68,21 +68,13 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
late MarineInSituSamplingService _marineInSituService;
|
late MarineInSituSamplingService _marineInSituService;
|
||||||
late MarineTarballSamplingService _marineTarballService;
|
late MarineTarballSamplingService _marineTarballService;
|
||||||
|
|
||||||
// --- START: MODIFIED STATE FOR DROPDOWN ---
|
|
||||||
String _selectedModule = 'Manual Sampling';
|
|
||||||
final List<String> _modules = ['Manual Sampling', 'Tarball Sampling', 'Pre-Sampling', 'Report'];
|
|
||||||
final TextEditingController _searchController = TextEditingController();
|
|
||||||
|
|
||||||
List<SubmissionLogEntry> _manualLogs = [];
|
List<SubmissionLogEntry> _manualLogs = [];
|
||||||
List<SubmissionLogEntry> _tarballLogs = [];
|
List<SubmissionLogEntry> _tarballLogs = [];
|
||||||
List<SubmissionLogEntry> _preSamplingLogs = []; // No data source, will be empty
|
|
||||||
List<SubmissionLogEntry> _reportLogs = []; // Will hold NPE logs
|
|
||||||
|
|
||||||
List<SubmissionLogEntry> _filteredManualLogs = [];
|
List<SubmissionLogEntry> _filteredManualLogs = [];
|
||||||
List<SubmissionLogEntry> _filteredTarballLogs = [];
|
List<SubmissionLogEntry> _filteredTarballLogs = [];
|
||||||
List<SubmissionLogEntry> _filteredPreSamplingLogs = [];
|
|
||||||
List<SubmissionLogEntry> _filteredReportLogs = [];
|
final TextEditingController _manualSearchController = TextEditingController();
|
||||||
// --- END: MODIFIED STATE ---
|
final TextEditingController _tarballSearchController = TextEditingController();
|
||||||
|
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
final Map<String, bool> _isResubmitting = {};
|
final Map<String, bool> _isResubmitting = {};
|
||||||
@ -92,7 +84,8 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
// MODIFIED: Service instantiations are removed from initState.
|
// MODIFIED: Service instantiations are removed from initState.
|
||||||
// They will be initialized in didChangeDependencies.
|
// They will be initialized in didChangeDependencies.
|
||||||
_searchController.addListener(_filterLogs); // Use single search controller
|
_manualSearchController.addListener(_filterLogs);
|
||||||
|
_tarballSearchController.addListener(_filterLogs);
|
||||||
_loadAllLogs();
|
_loadAllLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,46 +95,45 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
// Fetch the single, global instances of the services from the Provider tree.
|
// Fetch the single, global instances of the services from the Provider tree.
|
||||||
|
// --- START FIX: Added listen: false to prevent unnecessary rebuilds ---
|
||||||
_marineInSituService = Provider.of<MarineInSituSamplingService>(context, listen: false);
|
_marineInSituService = Provider.of<MarineInSituSamplingService>(context, listen: false);
|
||||||
_marineTarballService = Provider.of<MarineTarballSamplingService>(context, listen: false);
|
_marineTarballService = Provider.of<MarineTarballSamplingService>(context, listen: false);
|
||||||
|
// --- END FIX ---
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_searchController.dispose(); // Dispose single search controller
|
_manualSearchController.dispose();
|
||||||
|
_tarballSearchController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadAllLogs() async {
|
Future<void> _loadAllLogs() async {
|
||||||
setState(() => _isLoading = true);
|
setState(() => _isLoading = true);
|
||||||
|
|
||||||
// --- START MODIFICATION: Load logs sequentially to avoid parallel permission requests ---
|
|
||||||
// final [tarballLogs, inSituLogs, npeLogs] = await Future.wait([
|
|
||||||
// _localStorageService.getAllTarballLogs(),
|
|
||||||
// _localStorageService.getAllInSituLogs(),
|
|
||||||
// _localStorageService.getAllNpeLogs(), // Fetch NPE logs for "Report"
|
|
||||||
// ]);
|
|
||||||
final tarballLogs = await _localStorageService.getAllTarballLogs();
|
final tarballLogs = await _localStorageService.getAllTarballLogs();
|
||||||
final inSituLogs = await _localStorageService.getAllInSituLogs();
|
final inSituLogs = await _localStorageService.getAllInSituLogs();
|
||||||
final npeLogs = await _localStorageService.getAllNpeLogs();
|
|
||||||
// --- END MODIFICATION ---
|
|
||||||
|
|
||||||
final List<SubmissionLogEntry> tempManual = [];
|
final List<SubmissionLogEntry> tempManual = [];
|
||||||
final List<SubmissionLogEntry> tempTarball = [];
|
final List<SubmissionLogEntry> tempTarball = [];
|
||||||
final List<SubmissionLogEntry> tempReport = [];
|
|
||||||
final List<SubmissionLogEntry> tempPreSampling = []; // Empty list
|
|
||||||
|
|
||||||
// Process In-Situ (Manual Sampling)
|
|
||||||
for (var log in inSituLogs) {
|
for (var log in inSituLogs) {
|
||||||
|
// START FIX: Use backward-compatible keys to read the timestamp
|
||||||
final String dateStr = log['sampling_date'] ?? log['man_date'] ?? '';
|
final String dateStr = log['sampling_date'] ?? log['man_date'] ?? '';
|
||||||
final String timeStr = log['sampling_time'] ?? log['man_time'] ?? '';
|
final String timeStr = log['sampling_time'] ?? log['man_time'] ?? '';
|
||||||
|
// END FIX
|
||||||
|
|
||||||
|
// --- START FIX: Prevent fallback to DateTime.now() to make errors visible ---
|
||||||
final dt = DateTime.tryParse('$dateStr $timeStr');
|
final dt = DateTime.tryParse('$dateStr $timeStr');
|
||||||
|
// --- END FIX ---
|
||||||
|
|
||||||
tempManual.add(SubmissionLogEntry(
|
tempManual.add(SubmissionLogEntry(
|
||||||
type: 'Manual Sampling',
|
type: 'Manual Sampling',
|
||||||
title: log['selectedStation']?['man_station_name'] ?? 'Unknown Station',
|
title: log['selectedStation']?['man_station_name'] ?? 'Unknown Station',
|
||||||
stationCode: log['selectedStation']?['man_station_code'] ?? 'N/A',
|
stationCode: log['selectedStation']?['man_station_code'] ?? 'N/A',
|
||||||
|
// --- START FIX: Use the parsed date or a placeholder for invalid entries ---
|
||||||
submissionDateTime: dt ?? DateTime.fromMillisecondsSinceEpoch(0),
|
submissionDateTime: dt ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
// --- END FIX ---
|
||||||
reportId: log['reportId']?.toString(),
|
reportId: log['reportId']?.toString(),
|
||||||
status: log['submissionStatus'] ?? 'L1',
|
status: log['submissionStatus'] ?? 'L1',
|
||||||
message: log['submissionMessage'] ?? 'No status message.',
|
message: log['submissionMessage'] ?? 'No status message.',
|
||||||
@ -152,17 +144,15 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process Tarball
|
|
||||||
for (var log in tarballLogs) {
|
for (var log in tarballLogs) {
|
||||||
final dateStr = log['sampling_date'] ?? '';
|
final dateStr = log['sampling_date'] ?? '';
|
||||||
final timeStr = log['sampling_time'] ?? '';
|
final timeStr = log['sampling_time'] ?? '';
|
||||||
final dt = DateTime.tryParse('$dateStr $timeStr');
|
|
||||||
|
|
||||||
tempTarball.add(SubmissionLogEntry(
|
tempTarball.add(SubmissionLogEntry(
|
||||||
type: 'Tarball Sampling',
|
type: 'Tarball Sampling',
|
||||||
title: log['selectedStation']?['tbl_station_name'] ?? 'Unknown Station',
|
title: log['selectedStation']?['tbl_station_name'] ?? 'Unknown Station',
|
||||||
stationCode: log['selectedStation']?['tbl_station_code'] ?? 'N/A',
|
stationCode: log['selectedStation']?['tbl_station_code'] ?? 'N/A',
|
||||||
submissionDateTime: dt ?? DateTime.fromMillisecondsSinceEpoch(0),
|
submissionDateTime: DateTime.tryParse('$dateStr $timeStr') ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
reportId: log['reportId']?.toString(),
|
reportId: log['reportId']?.toString(),
|
||||||
status: log['submissionStatus'] ?? 'L1',
|
status: log['submissionStatus'] ?? 'L1',
|
||||||
message: log['submissionMessage'] ?? 'No status message.',
|
message: log['submissionMessage'] ?? 'No status message.',
|
||||||
@ -173,74 +163,28 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- START: ADDED NPE LOG PROCESSING ---
|
|
||||||
// Process NPE (Report)
|
|
||||||
for (var log in npeLogs) {
|
|
||||||
final dateStr = log['eventDate'] ?? '';
|
|
||||||
final timeStr = log['eventTime'] ?? '';
|
|
||||||
final dt = DateTime.tryParse('$dateStr $timeStr');
|
|
||||||
|
|
||||||
String stationName = 'N/A';
|
|
||||||
String stationCode = 'N/A';
|
|
||||||
if (log['selectedStation'] != null) {
|
|
||||||
stationName = log['selectedStation']['man_station_name'] ??
|
|
||||||
log['selectedStation']['tbl_station_name'] ??
|
|
||||||
'Unknown Station';
|
|
||||||
stationCode = log['selectedStation']['man_station_code'] ??
|
|
||||||
log['selectedStation']['tbl_station_code'] ??
|
|
||||||
'N/A';
|
|
||||||
} else if (log['locationDescription'] != null) {
|
|
||||||
stationName = log['locationDescription'];
|
|
||||||
stationCode = 'New Location';
|
|
||||||
}
|
|
||||||
|
|
||||||
tempReport.add(SubmissionLogEntry(
|
|
||||||
type: 'NPE Report',
|
|
||||||
title: stationName,
|
|
||||||
stationCode: stationCode,
|
|
||||||
submissionDateTime: dt ?? DateTime.fromMillisecondsSinceEpoch(0),
|
|
||||||
reportId: log['reportId']?.toString(),
|
|
||||||
status: log['submissionStatus'] ?? 'L1',
|
|
||||||
message: log['submissionMessage'] ?? 'No status message.',
|
|
||||||
rawData: log,
|
|
||||||
serverName: log['serverConfigName'] ?? 'Unknown Server',
|
|
||||||
apiStatusRaw: log['api_status'],
|
|
||||||
ftpStatusRaw: log['ftp_status'],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
// --- END: ADDED NPE LOG PROCESSING ---
|
|
||||||
|
|
||||||
// Sort all lists
|
|
||||||
tempManual.sort((a, b) => b.submissionDateTime.compareTo(a.submissionDateTime));
|
tempManual.sort((a, b) => b.submissionDateTime.compareTo(a.submissionDateTime));
|
||||||
tempTarball.sort((a, b) => b.submissionDateTime.compareTo(a.submissionDateTime));
|
tempTarball.sort((a, b) => b.submissionDateTime.compareTo(a.submissionDateTime));
|
||||||
tempReport.sort((a, b) => b.submissionDateTime.compareTo(a.submissionDateTime));
|
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_manualLogs = tempManual;
|
_manualLogs = tempManual;
|
||||||
_tarballLogs = tempTarball;
|
_tarballLogs = tempTarball;
|
||||||
_reportLogs = tempReport;
|
|
||||||
_preSamplingLogs = tempPreSampling; // Stays empty
|
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
_filterLogs(); // Apply initial filter
|
_filterLogs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- START: MODIFIED _filterLogs ---
|
|
||||||
void _filterLogs() {
|
void _filterLogs() {
|
||||||
final query = _searchController.text.toLowerCase();
|
final manualQuery = _manualSearchController.text.toLowerCase();
|
||||||
|
final tarballQuery = _tarballSearchController.text.toLowerCase();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
// We filter all lists regardless of selection, so data is ready
|
_filteredManualLogs = _manualLogs.where((log) => _logMatchesQuery(log, manualQuery)).toList();
|
||||||
// if the user switches modules.
|
_filteredTarballLogs = _tarballLogs.where((log) => _logMatchesQuery(log, tarballQuery)).toList();
|
||||||
_filteredManualLogs = _manualLogs.where((log) => _logMatchesQuery(log, query)).toList();
|
|
||||||
_filteredTarballLogs = _tarballLogs.where((log) => _logMatchesQuery(log, query)).toList();
|
|
||||||
_filteredPreSamplingLogs = _preSamplingLogs.where((log) => _logMatchesQuery(log, query)).toList();
|
|
||||||
_filteredReportLogs = _reportLogs.where((log) => _logMatchesQuery(log, query)).toList();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// --- END: MODIFIED _filterLogs ---
|
|
||||||
|
|
||||||
bool _logMatchesQuery(SubmissionLogEntry log, String query) {
|
bool _logMatchesQuery(SubmissionLogEntry log, String query) {
|
||||||
if (query.isEmpty) return true;
|
if (query.isEmpty) return true;
|
||||||
@ -322,13 +266,6 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
context: context,
|
context: context,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// --- ADD RESUBMIT FOR NPE ---
|
|
||||||
else if (log.type == 'NPE Report') {
|
|
||||||
// As of now, resubmission for NPE is not defined in the provided files.
|
|
||||||
// We will show a message and not attempt resubmission.
|
|
||||||
result = {'message': 'Resubmission for NPE Reports is not currently supported.'};
|
|
||||||
}
|
|
||||||
// --- END ADD ---
|
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@ -351,151 +288,75 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- START: MODIFIED build ---
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final hasAnyLogs = _manualLogs.isNotEmpty || _tarballLogs.isNotEmpty;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Marine Manual Data Status Log')),
|
appBar: AppBar(title: const Text('Marine Manual Data Status Log')),
|
||||||
body: _isLoading
|
body: _isLoading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: Padding(
|
: RefreshIndicator(
|
||||||
padding: const EdgeInsets.all(8.0),
|
onRefresh: _loadAllLogs,
|
||||||
child: Column(
|
child: !hasAnyLogs
|
||||||
|
? const Center(child: Text('No submission logs found.'))
|
||||||
|
: ListView(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
children: [
|
children: [
|
||||||
// --- WIDGET 1: DROPDOWN ---
|
_buildCategorySection('Manual Sampling', _filteredManualLogs, _manualSearchController),
|
||||||
DropdownButtonFormField<String>(
|
_buildCategorySection('Tarball Sampling', _filteredTarballLogs, _tarballSearchController),
|
||||||
value: _selectedModule,
|
|
||||||
items: _modules.map((module) {
|
|
||||||
return DropdownMenuItem(value: module, child: Text(module));
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (newValue) {
|
|
||||||
if (newValue != null) {
|
|
||||||
setState(() {
|
|
||||||
_selectedModule = newValue;
|
|
||||||
_searchController.clear(); // Clear search on module change
|
|
||||||
});
|
|
||||||
_filterLogs(); // Apply filter for new module
|
|
||||||
}
|
|
||||||
},
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Select Module',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 12.0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
|
|
||||||
// --- WIDGET 2: THE "BLACK BOX" CARD ---
|
|
||||||
Expanded(
|
|
||||||
child: Card(
|
|
||||||
// Use Card's color or specify black-ish if needed
|
|
||||||
// color: Theme.of(context).cardColor,
|
|
||||||
elevation: 2,
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
// --- SEARCH BAR (MOVED INSIDE CARD) ---
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: TextField(
|
|
||||||
controller: _searchController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'Search in $_selectedModule...',
|
|
||||||
prefixIcon: const Icon(Icons.search, size: 20),
|
|
||||||
isDense: true,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
suffixIcon: _searchController.text.isNotEmpty ? IconButton(
|
|
||||||
icon: const Icon(Icons.clear),
|
|
||||||
onPressed: () {
|
|
||||||
_searchController.clear();
|
|
||||||
},
|
|
||||||
) : null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
|
|
||||||
// --- LOG LIST (MOVED INSIDE CARD) ---
|
|
||||||
Expanded(
|
|
||||||
child: RefreshIndicator(
|
|
||||||
onRefresh: _loadAllLogs,
|
|
||||||
child: _buildCurrentModuleList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// --- END: MODIFIED build ---
|
|
||||||
|
|
||||||
// --- START: NEW WIDGET _buildCurrentModuleList ---
|
Widget _buildCategorySection(String category, List<SubmissionLogEntry> logs, TextEditingController searchController) {
|
||||||
/// Builds the list view based on the currently selected dropdown module.
|
return Card(
|
||||||
Widget _buildCurrentModuleList() {
|
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
switch (_selectedModule) {
|
child: Padding(
|
||||||
case 'Manual Sampling':
|
padding: const EdgeInsets.all(8.0),
|
||||||
return _buildLogList(
|
child: Column(
|
||||||
filteredLogs: _filteredManualLogs,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
totalLogCount: _manualLogs.length,
|
children: [
|
||||||
);
|
Text(category, style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold)),
|
||||||
case 'Tarball Sampling':
|
Padding(
|
||||||
return _buildLogList(
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
filteredLogs: _filteredTarballLogs,
|
child: TextField(
|
||||||
totalLogCount: _tarballLogs.length,
|
controller: searchController,
|
||||||
);
|
decoration: InputDecoration(
|
||||||
case 'Pre-Sampling':
|
hintText: 'Search in $category...',
|
||||||
return _buildLogList(
|
prefixIcon: const Icon(Icons.search, size: 20),
|
||||||
filteredLogs: _filteredPreSamplingLogs,
|
isDense: true,
|
||||||
totalLogCount: _preSamplingLogs.length,
|
border: const OutlineInputBorder(),
|
||||||
);
|
suffixIcon: searchController.text.isNotEmpty ? IconButton(
|
||||||
case 'Report':
|
icon: const Icon(Icons.clear),
|
||||||
return _buildLogList(
|
onPressed: () {
|
||||||
filteredLogs: _filteredReportLogs,
|
searchController.clear();
|
||||||
totalLogCount: _reportLogs.length,
|
},
|
||||||
);
|
) : null,
|
||||||
default:
|
),
|
||||||
return const Center(child: Text('Please select a module.'));
|
),
|
||||||
}
|
),
|
||||||
}
|
const Divider(),
|
||||||
// --- END: NEW WIDGET _buildCurrentModuleList ---
|
if (logs.isEmpty)
|
||||||
|
const Padding(
|
||||||
// --- START: MODIFIED WIDGET _buildLogList ---
|
padding: EdgeInsets.all(16.0),
|
||||||
/// A generic list builder that simply shows all filtered logs in a scrollable list.
|
child: Center(child: Text('No logs match your search in this category.')))
|
||||||
Widget _buildLogList({
|
else
|
||||||
required List<SubmissionLogEntry> filteredLogs,
|
ListView.builder(
|
||||||
required int totalLogCount,
|
shrinkWrap: true,
|
||||||
}) {
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
if (filteredLogs.isEmpty) {
|
itemCount: logs.length,
|
||||||
final String message = _searchController.text.isNotEmpty
|
itemBuilder: (context, index) {
|
||||||
? 'No logs match your search.'
|
return _buildLogListItem(logs[index]);
|
||||||
: (totalLogCount == 0 ? 'No submission logs found.' : 'No logs match your search.');
|
},
|
||||||
|
),
|
||||||
// Use a ListView to allow RefreshIndicator to work even when empty
|
],
|
||||||
return ListView(
|
),
|
||||||
children: [
|
),
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(32.0),
|
|
||||||
child: Center(child: Text(message)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Standard ListView.builder renders all filtered logs.
|
|
||||||
// It inherently only builds visible items, so it is efficient.
|
|
||||||
return ListView.builder(
|
|
||||||
itemCount: filteredLogs.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final log = filteredLogs[index];
|
|
||||||
return _buildLogListItem(log);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// --- END: MODIFIED WIDGET _buildLogList ---
|
|
||||||
|
|
||||||
Widget _buildLogListItem(SubmissionLogEntry log) {
|
Widget _buildLogListItem(SubmissionLogEntry log) {
|
||||||
final logKey = log.reportId ?? log.submissionDateTime.toIso8601String();
|
final logKey = log.reportId ?? log.submissionDateTime.toIso8601String();
|
||||||
@ -505,9 +366,9 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
// Define the different states based on the detailed status code.
|
// Define the different states based on the detailed status code.
|
||||||
final bool isFullSuccess = log.status == 'S4';
|
final bool isFullSuccess = log.status == 'S4';
|
||||||
final bool isPartialSuccess = log.status == 'S3' || log.status == 'L4';
|
final bool isPartialSuccess = log.status == 'S3' || log.status == 'L4';
|
||||||
final bool canResubmit = !isFullSuccess && log.type != 'NPE Report'; // --- MODIFIED: Disable resubmit for NPE
|
final bool canResubmit = !isFullSuccess; // Allow resubmission for partial success or failure.
|
||||||
// --- END: MODIFICATION FOR GRANULAR STATUS ICONS ---
|
|
||||||
|
|
||||||
|
// Determine the icon and color based on the state.
|
||||||
IconData statusIcon;
|
IconData statusIcon;
|
||||||
Color statusColor;
|
Color statusColor;
|
||||||
|
|
||||||
@ -521,6 +382,7 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
statusIcon = Icons.error_outline;
|
statusIcon = Icons.error_outline;
|
||||||
statusColor = Colors.red;
|
statusColor = Colors.red;
|
||||||
}
|
}
|
||||||
|
// --- END: MODIFICATION FOR GRANULAR STATUS ICONS ---
|
||||||
|
|
||||||
final titleWidget = RichText(
|
final titleWidget = RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
@ -561,7 +423,7 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
_buildDetailRow('Report ID:', log.reportId ?? 'N/A'),
|
_buildDetailRow('Report ID:', log.reportId ?? 'N/A'),
|
||||||
_buildDetailRow('Submission Type:', log.type),
|
_buildDetailRow('Submission Type:', log.type),
|
||||||
|
|
||||||
// --- START: ADDED BUTTONS ---
|
// --- START: ADDED BUTTONS AND GRANULAR STATUS ---
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -580,11 +442,10 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// --- END: ADDED BUTTONS ---
|
const Divider(height: 10),
|
||||||
|
_buildGranularStatus('API', log.apiStatusRaw),
|
||||||
const Divider(height: 10), // --- ADDED DIVIDER ---
|
_buildGranularStatus('FTP', log.ftpStatusRaw),
|
||||||
_buildGranularStatus('API', log.apiStatusRaw), // --- ADDED ---
|
// --- END: ADDED BUTTONS AND GRANULAR STATUS ---
|
||||||
_buildGranularStatus('FTP', log.ftpStatusRaw), // --- ADDED ---
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -592,7 +453,23 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- START: NEW HELPER WIDGETS FOR CATEGORIZED DIALOG ---
|
Widget _buildDetailRow(String label, String value) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(flex: 2, child: Text(label, style: const TextStyle(fontWeight: FontWeight.bold))),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(flex: 3, child: Text(value)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// --- START: ADDED METHODS FOR VIEW DATA / VIEW IMAGE FUNCTIONALITY ---
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
/// Builds a formatted category header row for the data table.
|
/// Builds a formatted category header row for the data table.
|
||||||
TableRow _buildCategoryRow(BuildContext context, String title, IconData icon) {
|
TableRow _buildCategoryRow(BuildContext context, String title, IconData icon) {
|
||||||
@ -611,7 +488,7 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
title,
|
title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 16,
|
fontSize: 14,
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -636,11 +513,24 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
|
||||||
child: Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
|
// --- START: MODIFICATION FOR FONT SIZE ---
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 11.0, // <-- ADJUST THIS VALUE AS NEEDED
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// --- END: MODIFICATION FOR FONT SIZE ---
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
|
||||||
child: Text(displayValue), // Use Text, NOT SelectableText
|
// --- START: MODIFICATION FOR FONT SIZE ---
|
||||||
|
child: Text(
|
||||||
|
displayValue,
|
||||||
|
style: const TextStyle(fontSize: 11.0), // <-- ADJUST THIS VALUE AS NEEDED
|
||||||
|
),
|
||||||
|
// --- END: MODIFICATION FOR FONT SIZE ---
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -653,11 +543,6 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
if (value is double && value == -999.0) return 'N/A';
|
if (value is double && value == -999.0) return 'N/A';
|
||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
// --- END: NEW HELPER WIDGETS ---
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// --- START OF MODIFIED FUNCTION ---
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/// Shows the categorized and formatted data log in a dialog
|
/// Shows the categorized and formatted data log in a dialog
|
||||||
void _showDataDialog(BuildContext context, SubmissionLogEntry log) {
|
void _showDataDialog(BuildContext context, SubmissionLogEntry log) {
|
||||||
@ -666,7 +551,6 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
|
|
||||||
// --- 1. Sampling Info ---
|
// --- 1. Sampling Info ---
|
||||||
tableRows.add(_buildCategoryRow(context, 'Sampling Info', Icons.calendar_today));
|
tableRows.add(_buildCategoryRow(context, 'Sampling Info', Icons.calendar_today));
|
||||||
// --- MODIFIED: Added keys for NPE Report
|
|
||||||
tableRows.add(_buildDataTableRow('Date', _getString(data, 'sampling_date') ?? _getString(data, 'man_date') ?? _getString(data, 'eventDate')));
|
tableRows.add(_buildDataTableRow('Date', _getString(data, 'sampling_date') ?? _getString(data, 'man_date') ?? _getString(data, 'eventDate')));
|
||||||
tableRows.add(_buildDataTableRow('Time', _getString(data, 'sampling_time') ?? _getString(data, 'man_time') ?? _getString(data, 'eventTime')));
|
tableRows.add(_buildDataTableRow('Time', _getString(data, 'sampling_time') ?? _getString(data, 'man_time') ?? _getString(data, 'eventTime')));
|
||||||
|
|
||||||
@ -694,7 +578,6 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
tableRows.add(_buildDataTableRow('Station Latitude', stationLat));
|
tableRows.add(_buildDataTableRow('Station Latitude', stationLat));
|
||||||
tableRows.add(_buildDataTableRow('Station Longitude', stationLon));
|
tableRows.add(_buildDataTableRow('Station Longitude', stationLon));
|
||||||
|
|
||||||
// --- MODIFIED: Added 'latitude'/'longitude' keys for NPE
|
|
||||||
tableRows.add(_buildDataTableRow('Current Latitude', _getString(data, 'current_latitude') ?? _getString(data, 'currentLatitude') ?? _getString(data, 'latitude')));
|
tableRows.add(_buildDataTableRow('Current Latitude', _getString(data, 'current_latitude') ?? _getString(data, 'currentLatitude') ?? _getString(data, 'latitude')));
|
||||||
tableRows.add(_buildDataTableRow('Current Longitude', _getString(data, 'current_longitude') ?? _getString(data, 'currentLongitude') ?? _getString(data, 'longitude')));
|
tableRows.add(_buildDataTableRow('Current Longitude', _getString(data, 'current_longitude') ?? _getString(data, 'currentLongitude') ?? _getString(data, 'longitude')));
|
||||||
|
|
||||||
@ -708,7 +591,6 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
tableRows.add(_buildDataTableRow('Sea', _getString(data, 'sea_condition') ?? _getString(data, 'seaCondition') ?? _getString(data, 'sea_condition_manual')));
|
tableRows.add(_buildDataTableRow('Sea', _getString(data, 'sea_condition') ?? _getString(data, 'seaCondition') ?? _getString(data, 'sea_condition_manual')));
|
||||||
tableRows.add(_buildDataTableRow('Weather', _getString(data, 'weather') ?? _getString(data, 'weather_manual')));
|
tableRows.add(_buildDataTableRow('Weather', _getString(data, 'weather') ?? _getString(data, 'weather_manual')));
|
||||||
|
|
||||||
// --- MODIFIED: Use correct plural keys first
|
|
||||||
tableRows.add(_buildDataTableRow('Event Remarks', _getString(data, 'event_remarks') ?? _getString(data, 'man_event_remark') ?? _getString(data, 'eventRemark')));
|
tableRows.add(_buildDataTableRow('Event Remarks', _getString(data, 'event_remarks') ?? _getString(data, 'man_event_remark') ?? _getString(data, 'eventRemark')));
|
||||||
tableRows.add(_buildDataTableRow('Lab Remarks', _getString(data, 'lab_remarks') ?? _getString(data, 'man_lab_remark') ?? _getString(data, 'labRemark')));
|
tableRows.add(_buildDataTableRow('Lab Remarks', _getString(data, 'lab_remarks') ?? _getString(data, 'man_lab_remark') ?? _getString(data, 'labRemark')));
|
||||||
}
|
}
|
||||||
@ -790,14 +672,9 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// --- END OF MODIFIED FUNCTION ---
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/// Shows the image gallery dialog
|
/// Shows the image gallery dialog
|
||||||
void _showImageDialog(BuildContext context, SubmissionLogEntry log) {
|
void _showImageDialog(BuildContext context, SubmissionLogEntry log) {
|
||||||
|
|
||||||
// --- START: MODIFIED to handle all log types ---
|
|
||||||
final List<ImageLogEntry> imageEntries = [];
|
final List<ImageLogEntry> imageEntries = [];
|
||||||
|
|
||||||
if (log.type == 'Manual Sampling') {
|
if (log.type == 'Manual Sampling') {
|
||||||
@ -839,7 +716,6 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
};
|
};
|
||||||
_addImagesToList(log, imageRemarkMap, imageEntries);
|
_addImagesToList(log, imageRemarkMap, imageEntries);
|
||||||
}
|
}
|
||||||
// --- END: MODIFIED ---
|
|
||||||
|
|
||||||
|
|
||||||
if (imageEntries.isEmpty) {
|
if (imageEntries.isEmpty) {
|
||||||
@ -952,7 +828,6 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// --- END: NEW HELPER ---
|
|
||||||
|
|
||||||
Widget _buildGranularStatus(String type, String? jsonStatus) {
|
Widget _buildGranularStatus(String type, String? jsonStatus) {
|
||||||
if (jsonStatus == null || jsonStatus.isEmpty) {
|
if (jsonStatus == null || jsonStatus.isEmpty) {
|
||||||
@ -999,18 +874,7 @@ class _MarineManualDataStatusLogState extends State<MarineManualDataStatusLog> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// =========================================================================
|
||||||
Widget _buildDetailRow(String label, String value) {
|
// --- END: ADDED METHODS FOR VIEW DATA / VIEW IMAGE FUNCTIONALITY ---
|
||||||
return Padding(
|
// =========================================================================
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Expanded(flex: 2, child: Text(label, style: const TextStyle(fontWeight: FontWeight.bold))),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(flex: 3, child: Text(value)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
1291
lib/screens/marine/manual/marine_manual_report_status_log.dart
Normal file
1291
lib/screens/marine/manual/marine_manual_report_status_log.dart
Normal file
File diff suppressed because it is too large
Load Diff
@ -41,6 +41,9 @@ class MarineHomePage extends StatelessWidget {
|
|||||||
SidebarItem(icon: Icons.article, label: "Data Log", route: '/marine/manual/data-log'),
|
SidebarItem(icon: Icons.article, label: "Data Log", route: '/marine/manual/data-log'),
|
||||||
SidebarItem(icon: Icons.image, label: "Image Request", route: '/marine/manual/image-request'),
|
SidebarItem(icon: Icons.image, label: "Image Request", route: '/marine/manual/image-request'),
|
||||||
SidebarItem(icon: Icons.receipt_long, label: "Report", route: '/marine/manual/report'),
|
SidebarItem(icon: Icons.receipt_long, label: "Report", route: '/marine/manual/report'),
|
||||||
|
// *** START: ADDED NEW MENU ITEM ***
|
||||||
|
SidebarItem(icon: Icons.history_edu_outlined, label: "Report Status Log", route: '/marine/manual/report-log'),
|
||||||
|
// *** END: ADDED NEW MENU ITEM ***
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SidebarItem(
|
SidebarItem(
|
||||||
|
|||||||
@ -14,6 +14,11 @@ import '../models/air_collection_data.dart';
|
|||||||
import '../models/tarball_data.dart';
|
import '../models/tarball_data.dart';
|
||||||
import '../models/in_situ_sampling_data.dart';
|
import '../models/in_situ_sampling_data.dart';
|
||||||
import '../models/marine_manual_npe_report_data.dart';
|
import '../models/marine_manual_npe_report_data.dart';
|
||||||
|
// --- ADDED: Imports for new data models ---
|
||||||
|
import '../models/marine_manual_pre_departure_checklist_data.dart';
|
||||||
|
import '../models/marine_manual_sonde_calibration_data.dart';
|
||||||
|
import '../models/marine_manual_equipment_maintenance_data.dart';
|
||||||
|
// --- END ADDED ---
|
||||||
import '../models/river_in_situ_sampling_data.dart';
|
import '../models/river_in_situ_sampling_data.dart';
|
||||||
import '../models/river_manual_triennial_sampling_data.dart';
|
import '../models/river_manual_triennial_sampling_data.dart';
|
||||||
// --- ADDED IMPORT ---
|
// --- ADDED IMPORT ---
|
||||||
@ -511,7 +516,7 @@ class LocalStorageService {
|
|||||||
try {
|
try {
|
||||||
final String originalFileName = p.basename(imageFile.path);
|
final String originalFileName = p.basename(imageFile.path);
|
||||||
final File newFile = await imageFile.copy(p.join(eventDir.path, originalFileName));
|
final File newFile = await imageFile.copy(p.join(eventDir.path, originalFileName));
|
||||||
jsonData[entry.key] = newFile.path;
|
jsonData['image${entry.key.split('_').last}Path'] = newFile.path; // Save as image1Path, etc.
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error processing NPE image file ${imageFile.path}: $e");
|
debugPrint("Error processing NPE image file ${imageFile.path}: $e");
|
||||||
}
|
}
|
||||||
@ -576,6 +581,268 @@ class LocalStorageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// --- START: Added Part 4.6: Marine Pre-Departure Methods ---
|
||||||
|
// =======================================================================
|
||||||
|
|
||||||
|
Future<Directory?> _getPreDepartureBaseDir({required String serverName}) async {
|
||||||
|
final mmsv4Dir = await _getPublicMMSV4Directory(serverName: serverName);
|
||||||
|
if (mmsv4Dir == null) return null;
|
||||||
|
|
||||||
|
final logDir = Directory(p.join(mmsv4Dir.path, 'marine', 'marine_pre_departure'));
|
||||||
|
if (!await logDir.exists()) {
|
||||||
|
await logDir.create(recursive: true);
|
||||||
|
}
|
||||||
|
return logDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> savePreDepartureData(MarineManualPreDepartureChecklistData data, {required String serverName}) async {
|
||||||
|
final baseDir = await _getPreDepartureBaseDir(serverName: serverName);
|
||||||
|
if (baseDir == null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final timestamp = data.submissionDate ?? DateTime.now().toIso8601String();
|
||||||
|
final eventFolderName = "checklist_$timestamp";
|
||||||
|
final eventDir = Directory(p.join(baseDir.path, eventFolderName));
|
||||||
|
if (!await eventDir.exists()) {
|
||||||
|
await eventDir.create(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- START: MODIFIED BLOCK ---
|
||||||
|
// Use toDbJson() for a complete, consistent log
|
||||||
|
final Map<String, dynamic> jsonData = data.toDbJson();
|
||||||
|
jsonData['serverConfigName'] = serverName;
|
||||||
|
// All other fields are now included in toDbJson()
|
||||||
|
// --- END: MODIFIED BLOCK ---
|
||||||
|
|
||||||
|
final jsonFile = File(p.join(eventDir.path, 'data.json'));
|
||||||
|
await jsonFile.writeAsString(jsonEncode(jsonData));
|
||||||
|
debugPrint("Pre-Departure log saved to: ${jsonFile.path}");
|
||||||
|
return eventDir.path;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error saving Pre-Departure log to local storage: $e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> getAllPreDepartureLogs() async {
|
||||||
|
final mmsv4Root = await _getPublicMMSV4Directory(serverName: '');
|
||||||
|
if (mmsv4Root == null || !await mmsv4Root.exists()) return [];
|
||||||
|
|
||||||
|
final List<Map<String, dynamic>> allLogs = [];
|
||||||
|
final serverDirs = mmsv4Root.listSync().whereType<Directory>();
|
||||||
|
|
||||||
|
for (var serverDir in serverDirs) {
|
||||||
|
final baseDir = Directory(p.join(serverDir.path, 'marine', 'marine_pre_departure'));
|
||||||
|
if (!await baseDir.exists()) continue;
|
||||||
|
try {
|
||||||
|
final entities = baseDir.listSync();
|
||||||
|
for (var entity in entities) {
|
||||||
|
if (entity is Directory) {
|
||||||
|
final jsonFile = File(p.join(entity.path, 'data.json'));
|
||||||
|
if (await jsonFile.exists()) {
|
||||||
|
final data = jsonDecode(await jsonFile.readAsString()) as Map<String, dynamic>;
|
||||||
|
data['logDirectory'] = entity.path;
|
||||||
|
allLogs.add(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error reading Pre-Departure logs from ${baseDir.path}: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allLogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updatePreDepartureLog(Map<String, dynamic> updatedLogData) async {
|
||||||
|
final logDir = updatedLogData['logDirectory'];
|
||||||
|
if (logDir == null) return;
|
||||||
|
try {
|
||||||
|
final jsonFile = File(p.join(logDir, 'data.json'));
|
||||||
|
if (await jsonFile.exists()) {
|
||||||
|
updatedLogData.remove('isResubmitting');
|
||||||
|
await jsonFile.writeAsString(jsonEncode(updatedLogData));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error updating Pre-Departure log: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// --- START: Added Part 4.7: Marine Sonde Calibration Methods ---
|
||||||
|
// =======================================================================
|
||||||
|
|
||||||
|
Future<Directory?> _getSondeCalibrationBaseDir({required String serverName}) async {
|
||||||
|
final mmsv4Dir = await _getPublicMMSV4Directory(serverName: serverName);
|
||||||
|
if (mmsv4Dir == null) return null;
|
||||||
|
|
||||||
|
final logDir = Directory(p.join(mmsv4Dir.path, 'marine', 'marine_sonde_calibration'));
|
||||||
|
if (!await logDir.exists()) {
|
||||||
|
await logDir.create(recursive: true);
|
||||||
|
}
|
||||||
|
return logDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> saveSondeCalibrationData(MarineManualSondeCalibrationData data, {required String serverName}) async {
|
||||||
|
final baseDir = await _getSondeCalibrationBaseDir(serverName: serverName);
|
||||||
|
if (baseDir == null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final timestamp = data.startDateTime?.replaceAll(':', '-').replaceAll(' ', '_') ?? DateTime.now().toIso8601String();
|
||||||
|
final eventFolderName = "calibration_${data.sondeSerialNumber}_$timestamp";
|
||||||
|
final eventDir = Directory(p.join(baseDir.path, eventFolderName));
|
||||||
|
if (!await eventDir.exists()) {
|
||||||
|
await eventDir.create(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- START: MODIFIED BLOCK ---
|
||||||
|
// Use toDbJson() for a complete, consistent log
|
||||||
|
final Map<String, dynamic> jsonData = data.toDbJson();
|
||||||
|
jsonData['serverConfigName'] = serverName;
|
||||||
|
// All other fields are now included in toDbJson()
|
||||||
|
// --- END: MODIFIED BLOCK ---
|
||||||
|
|
||||||
|
final jsonFile = File(p.join(eventDir.path, 'data.json'));
|
||||||
|
await jsonFile.writeAsString(jsonEncode(jsonData));
|
||||||
|
debugPrint("Sonde Calibration log saved to: ${jsonFile.path}");
|
||||||
|
return eventDir.path;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error saving Sonde Calibration log to local storage: $e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> getAllSondeCalibrationLogs() async {
|
||||||
|
final mmsv4Root = await _getPublicMMSV4Directory(serverName: '');
|
||||||
|
if (mmsv4Root == null || !await mmsv4Root.exists()) return [];
|
||||||
|
|
||||||
|
final List<Map<String, dynamic>> allLogs = [];
|
||||||
|
final serverDirs = mmsv4Root.listSync().whereType<Directory>();
|
||||||
|
|
||||||
|
for (var serverDir in serverDirs) {
|
||||||
|
final baseDir = Directory(p.join(serverDir.path, 'marine', 'marine_sonde_calibration'));
|
||||||
|
if (!await baseDir.exists()) continue;
|
||||||
|
try {
|
||||||
|
final entities = baseDir.listSync();
|
||||||
|
for (var entity in entities) {
|
||||||
|
if (entity is Directory) {
|
||||||
|
final jsonFile = File(p.join(entity.path, 'data.json'));
|
||||||
|
if (await jsonFile.exists()) {
|
||||||
|
final data = jsonDecode(await jsonFile.readAsString()) as Map<String, dynamic>;
|
||||||
|
data['logDirectory'] = entity.path;
|
||||||
|
allLogs.add(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error reading Sonde Calibration logs from ${baseDir.path}: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allLogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateSondeCalibrationLog(Map<String, dynamic> updatedLogData) async {
|
||||||
|
final logDir = updatedLogData['logDirectory'];
|
||||||
|
if (logDir == null) return;
|
||||||
|
try {
|
||||||
|
final jsonFile = File(p.join(logDir, 'data.json'));
|
||||||
|
if (await jsonFile.exists()) {
|
||||||
|
updatedLogData.remove('isResubmitting');
|
||||||
|
await jsonFile.writeAsString(jsonEncode(updatedLogData));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error updating Sonde Calibration log: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// --- START: Added Part 4.8: Marine Equipment Maintenance Methods ---
|
||||||
|
// =======================================================================
|
||||||
|
|
||||||
|
Future<Directory?> _getEquipmentMaintenanceBaseDir({required String serverName}) async {
|
||||||
|
final mmsv4Dir = await _getPublicMMSV4Directory(serverName: serverName);
|
||||||
|
if (mmsv4Dir == null) return null;
|
||||||
|
|
||||||
|
final logDir = Directory(p.join(mmsv4Dir.path, 'marine', 'marine_equipment_maintenance'));
|
||||||
|
if (!await logDir.exists()) {
|
||||||
|
await logDir.create(recursive: true);
|
||||||
|
}
|
||||||
|
return logDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> saveEquipmentMaintenanceData(MarineManualEquipmentMaintenanceData data, {required String serverName}) async {
|
||||||
|
final baseDir = await _getEquipmentMaintenanceBaseDir(serverName: serverName);
|
||||||
|
if (baseDir == null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final timestamp = data.maintenanceDate ?? DateTime.now().toIso8601String();
|
||||||
|
final eventFolderName = "maintenance_$timestamp";
|
||||||
|
final eventDir = Directory(p.join(baseDir.path, eventFolderName));
|
||||||
|
if (!await eventDir.exists()) {
|
||||||
|
await eventDir.create(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- START: MODIFIED BLOCK ---
|
||||||
|
// Use toDbJson() for a complete, consistent log
|
||||||
|
final Map<String, dynamic> jsonData = data.toDbJson();
|
||||||
|
jsonData['serverConfigName'] = serverName;
|
||||||
|
// All other fields are now included in toDbJson()
|
||||||
|
// --- END: MODIFIED BLOCK ---
|
||||||
|
|
||||||
|
final jsonFile = File(p.join(eventDir.path, 'data.json'));
|
||||||
|
await jsonFile.writeAsString(jsonEncode(jsonData));
|
||||||
|
debugPrint("Equipment Maintenance log saved to: ${jsonFile.path}");
|
||||||
|
return eventDir.path;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error saving Equipment Maintenance log to local storage: $e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> getAllEquipmentMaintenanceLogs() async {
|
||||||
|
final mmsv4Root = await _getPublicMMSV4Directory(serverName: '');
|
||||||
|
if (mmsv4Root == null || !await mmsv4Root.exists()) return [];
|
||||||
|
|
||||||
|
final List<Map<String, dynamic>> allLogs = [];
|
||||||
|
final serverDirs = mmsv4Root.listSync().whereType<Directory>();
|
||||||
|
|
||||||
|
for (var serverDir in serverDirs) {
|
||||||
|
final baseDir = Directory(p.join(serverDir.path, 'marine', 'marine_equipment_maintenance'));
|
||||||
|
if (!await baseDir.exists()) continue;
|
||||||
|
try {
|
||||||
|
final entities = baseDir.listSync();
|
||||||
|
for (var entity in entities) {
|
||||||
|
if (entity is Directory) {
|
||||||
|
final jsonFile = File(p.join(entity.path, 'data.json'));
|
||||||
|
if (await jsonFile.exists()) {
|
||||||
|
final data = jsonDecode(await jsonFile.readAsString()) as Map<String, dynamic>;
|
||||||
|
data['logDirectory'] = entity.path;
|
||||||
|
allLogs.add(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error reading Equipment Maintenance logs from ${baseDir.path}: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allLogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateEquipmentMaintenanceLog(Map<String, dynamic> updatedLogData) async {
|
||||||
|
final logDir = updatedLogData['logDirectory'];
|
||||||
|
if (logDir == null) return;
|
||||||
|
try {
|
||||||
|
final jsonFile = File(p.join(logDir, 'data.json'));
|
||||||
|
if (await jsonFile.exists()) {
|
||||||
|
updatedLogData.remove('isResubmitting');
|
||||||
|
await jsonFile.writeAsString(jsonEncode(updatedLogData));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error updating Equipment Maintenance log: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// =======================================================================
|
// =======================================================================
|
||||||
// Part 5: River In-Situ Specific Methods (LOGGING RESTORED)
|
// Part 5: River In-Situ Specific Methods (LOGGING RESTORED)
|
||||||
// =======================================================================
|
// =======================================================================
|
||||||
|
|||||||
@ -2,57 +2,259 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
|
||||||
import '../auth_provider.dart';
|
import '../auth_provider.dart';
|
||||||
import '../models/marine_manual_equipment_maintenance_data.dart';
|
import '../models/marine_manual_equipment_maintenance_data.dart';
|
||||||
import 'api_service.dart';
|
import 'api_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/database_helper.dart';
|
import 'package:environment_monitoring_app/services/database_helper.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/local_storage_service.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/server_config_service.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/retry_service.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/submission_api_service.dart';
|
||||||
|
|
||||||
import 'base_api_service.dart'; // Import for SessionExpiredException
|
import 'base_api_service.dart'; // Import for SessionExpiredException
|
||||||
|
|
||||||
class MarineManualEquipmentMaintenanceService {
|
class MarineManualEquipmentMaintenanceService {
|
||||||
final ApiService _apiService;
|
// Use the new generic submission service
|
||||||
|
final SubmissionApiService _submissionApiService = SubmissionApiService();
|
||||||
|
final LocalStorageService _localStorageService = LocalStorageService();
|
||||||
|
final ServerConfigService _serverConfigService = ServerConfigService();
|
||||||
|
final DatabaseHelper _dbHelper = DatabaseHelper();
|
||||||
|
final RetryService _retryService = RetryService();
|
||||||
|
|
||||||
|
// Keep ApiService for getPreviousMaintenanceLogs
|
||||||
|
final ApiService _apiService;
|
||||||
MarineManualEquipmentMaintenanceService(this._apiService);
|
MarineManualEquipmentMaintenanceService(this._apiService);
|
||||||
|
|
||||||
|
// *** START: Renamed this method ***
|
||||||
|
/// Main submission method with online/offline branching logic
|
||||||
Future<Map<String, dynamic>> submitMaintenanceReport({
|
Future<Map<String, dynamic>> submitMaintenanceReport({
|
||||||
|
// *** END: Renamed this method ***
|
||||||
required MarineManualEquipmentMaintenanceData data,
|
required MarineManualEquipmentMaintenanceData data,
|
||||||
required AuthProvider authProvider,
|
required AuthProvider authProvider,
|
||||||
|
List<Map<String, dynamic>>? appSettings, // Added for consistency
|
||||||
|
BuildContext? context, // Added for consistency
|
||||||
|
String? logDirectory,
|
||||||
}) async {
|
}) async {
|
||||||
|
const String moduleName = 'marine_equipment_maintenance';
|
||||||
|
|
||||||
|
// --- START: ADDED LINE ---
|
||||||
|
// Populate the user name from the AuthProvider
|
||||||
|
data.conductedByUserName = authProvider.profileData?['first_name'] as String?;
|
||||||
|
// --- END: ADDED LINE ---
|
||||||
|
|
||||||
|
final connectivityResult = await Connectivity().checkConnectivity();
|
||||||
|
bool isOnline = connectivityResult != ConnectivityResult.none;
|
||||||
|
bool isOfflineSession = authProvider.isLoggedIn &&
|
||||||
|
(authProvider.profileData?['token']
|
||||||
|
?.startsWith("offline-session-") ??
|
||||||
|
false);
|
||||||
|
|
||||||
|
if (isOnline && isOfflineSession) {
|
||||||
|
debugPrint(
|
||||||
|
"$moduleName submission online during offline session. Attempting auto-relogin...");
|
||||||
|
final bool transitionSuccess =
|
||||||
|
await authProvider.checkAndTransitionToOnlineSession();
|
||||||
|
if (transitionSuccess) {
|
||||||
|
isOfflineSession = false;
|
||||||
|
} else {
|
||||||
|
isOnline = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOnline && !isOfflineSession) {
|
||||||
|
debugPrint("Proceeding with direct ONLINE $moduleName submission...");
|
||||||
|
return await _performOnlineSubmission(
|
||||||
|
data: data,
|
||||||
|
moduleName: moduleName,
|
||||||
|
authProvider: authProvider,
|
||||||
|
logDirectory: logDirectory,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
debugPrint("Proceeding with OFFLINE $moduleName queuing mechanism...");
|
||||||
|
return await _performOfflineQueuing(
|
||||||
|
data: data,
|
||||||
|
moduleName: moduleName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles the direct online submission.
|
||||||
|
Future<Map<String, dynamic>> _performOnlineSubmission({
|
||||||
|
required MarineManualEquipmentMaintenanceData data,
|
||||||
|
required String moduleName,
|
||||||
|
required AuthProvider authProvider,
|
||||||
|
String? logDirectory,
|
||||||
|
}) async {
|
||||||
|
final serverName =
|
||||||
|
(await _serverConfigService.getActiveApiConfig())?['config_name']
|
||||||
|
as String? ??
|
||||||
|
'Default';
|
||||||
|
Map<String, dynamic> apiResult;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call the existing method in MarineApiService
|
apiResult = await _submissionApiService.submitPost(
|
||||||
return await _apiService.marine.submitMaintenanceLog(data);
|
moduleName: moduleName,
|
||||||
|
endpoint: 'marine/maintenance', // Endpoint from marine_api_service.dart
|
||||||
|
body: data.toApiFormData(),
|
||||||
|
);
|
||||||
} on SessionExpiredException {
|
} on SessionExpiredException {
|
||||||
// Handle session expiry by attempting a silent relogin
|
|
||||||
final bool reloginSuccess = await authProvider.attemptSilentRelogin();
|
final bool reloginSuccess = await authProvider.attemptSilentRelogin();
|
||||||
if (reloginSuccess) {
|
if (reloginSuccess) {
|
||||||
// Retry the submission once if relogin was successful
|
apiResult = await _submissionApiService.submitPost(
|
||||||
return await _apiService.marine.submitMaintenanceLog(data);
|
moduleName: moduleName,
|
||||||
|
endpoint: 'marine/maintenance',
|
||||||
|
body: data.toApiFormData(),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return {
|
apiResult = {
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Session expired. Please log in again.'
|
'message': 'Session expired. Please log in again.'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} on SocketException {
|
} on SocketException catch (e) {
|
||||||
// Handle network errors
|
apiResult = {
|
||||||
return {
|
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Submission failed. Please check your network connection.'
|
'message': "API submission failed with network error: $e"
|
||||||
};
|
};
|
||||||
} on TimeoutException {
|
// submission_api_service will queue this failure
|
||||||
// Handle timeout errors
|
} on TimeoutException catch (e) {
|
||||||
return {
|
apiResult = {
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Submission timed out. Please check your network connection.'
|
'message': "API submission timed out: $e"
|
||||||
};
|
};
|
||||||
|
// submission_api_service will queue this failure
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Handle any other unexpected errors
|
apiResult = {
|
||||||
return {'success': false, 'message': 'An unexpected error occurred: $e'};
|
'success': false,
|
||||||
|
'message': 'An unexpected error occurred: $e'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log the final result
|
||||||
|
final bool overallSuccess = apiResult['success'] == true;
|
||||||
|
final String finalMessage =
|
||||||
|
apiResult['message'] ?? (overallSuccess ? 'Submission successful.' : 'Submission failed.');
|
||||||
|
final String finalStatus = overallSuccess ? 'S4' : 'L1'; // S4 = API Success
|
||||||
|
|
||||||
|
if (overallSuccess) {
|
||||||
|
// Assuming the API returns an ID. Adjust 'maintenance_id' if needed.
|
||||||
|
data.reportId = apiResult['data']?['maintenance_id']?.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _logAndSave(
|
||||||
|
data: data,
|
||||||
|
status: finalStatus,
|
||||||
|
message: finalMessage,
|
||||||
|
apiResult: apiResult,
|
||||||
|
serverName: serverName,
|
||||||
|
logDirectory: logDirectory,
|
||||||
|
);
|
||||||
|
|
||||||
|
return apiResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles saving the submission to local storage and queuing for retry.
|
||||||
|
Future<Map<String, dynamic>> _performOfflineQueuing({
|
||||||
|
required MarineManualEquipmentMaintenanceData data,
|
||||||
|
required String moduleName,
|
||||||
|
}) async {
|
||||||
|
final serverConfig = await _serverConfigService.getActiveApiConfig();
|
||||||
|
final serverName =
|
||||||
|
serverConfig?['config_name'] as String? ?? 'Default';
|
||||||
|
|
||||||
|
data.submissionStatus = 'L1';
|
||||||
|
data.submissionMessage = 'Equipment Maintenance queued due to being offline.';
|
||||||
|
|
||||||
|
// This method is added to LocalStorageService
|
||||||
|
final String? localLogPath =
|
||||||
|
await _localStorageService.saveEquipmentMaintenanceData(data, serverName: serverName);
|
||||||
|
|
||||||
|
if (localLogPath == null) {
|
||||||
|
const message =
|
||||||
|
"Failed to save Equipment Maintenance to local device storage.";
|
||||||
|
await _logAndSave(
|
||||||
|
data: data,
|
||||||
|
status: 'Error',
|
||||||
|
message: message,
|
||||||
|
apiResult: {},
|
||||||
|
serverName: serverName);
|
||||||
|
return {'success': false, 'message': message};
|
||||||
|
}
|
||||||
|
|
||||||
|
await _retryService.queueTask(
|
||||||
|
type: 'equipment_maintenance_submission', // New task type
|
||||||
|
payload: {
|
||||||
|
'module': moduleName,
|
||||||
|
'localLogPath': localLogPath,
|
||||||
|
'serverConfig': serverConfig,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const successMessage =
|
||||||
|
"No internet connection. Equipment Maintenance has been saved and queued for upload.";
|
||||||
|
return {'success': true, 'message': successMessage};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs the submission to the local file system and the central SQL database.
|
||||||
|
Future<void> _logAndSave({
|
||||||
|
required MarineManualEquipmentMaintenanceData data,
|
||||||
|
required String status,
|
||||||
|
required String message,
|
||||||
|
required Map<String, dynamic> apiResult,
|
||||||
|
required String serverName,
|
||||||
|
String? logDirectory,
|
||||||
|
}) async {
|
||||||
|
data.submissionStatus = status;
|
||||||
|
data.submissionMessage = message;
|
||||||
|
|
||||||
|
final fileTimestamp = data.maintenanceDate ?? DateTime.now().toIso8601String();
|
||||||
|
|
||||||
|
if (logDirectory != null) {
|
||||||
|
// This is an update to an existing log file
|
||||||
|
// --- START: MODIFIED BLOCK ---
|
||||||
|
final Map<String, dynamic> updatedLogData = data.toDbJson();
|
||||||
|
// Add metadata
|
||||||
|
updatedLogData['submissionStatus'] = status;
|
||||||
|
updatedLogData['submissionMessage'] = message;
|
||||||
|
updatedLogData['logDirectory'] = logDirectory;
|
||||||
|
updatedLogData['serverConfigName'] = serverName;
|
||||||
|
updatedLogData['api_status'] = jsonEncode(apiResult);
|
||||||
|
// All other fields (ysiSondeChecks, etc.) are now in toDbJson()
|
||||||
|
// --- END: MODIFIED BLOCK ---
|
||||||
|
|
||||||
|
// This method is added to LocalStorageService
|
||||||
|
await _localStorageService.updateEquipmentMaintenanceLog(updatedLogData);
|
||||||
|
} else {
|
||||||
|
// This is a new log
|
||||||
|
// This method is added to LocalStorageService
|
||||||
|
await _localStorageService.saveEquipmentMaintenanceData(data, serverName: serverName);
|
||||||
|
}
|
||||||
|
|
||||||
|
final logData = {
|
||||||
|
'submission_id': data.reportId ?? fileTimestamp,
|
||||||
|
'module': 'marine',
|
||||||
|
'type': 'Equipment Maintenance',
|
||||||
|
'status': data.submissionStatus,
|
||||||
|
'message': data.submissionMessage,
|
||||||
|
'report_id': data.reportId,
|
||||||
|
'created_at': DateTime.now().toIso8601String(),
|
||||||
|
// --- START: MODIFIED LINE ---
|
||||||
|
'form_data': jsonEncode(data.toDbJson()), // Log the full DbJson
|
||||||
|
// --- END: MODIFIED LINE ---
|
||||||
|
'image_data': null, // No images
|
||||||
|
'server_name': serverName,
|
||||||
|
'api_status': jsonEncode(apiResult),
|
||||||
|
'ftp_status': null, // No FTP
|
||||||
|
};
|
||||||
|
await _dbHelper.saveSubmissionLog(logData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches previous maintenance logs to populate the form
|
/// Fetches previous maintenance logs to populate the form
|
||||||
|
/// THIS METHOD IS UNCHANGED as it's a simple GET request.
|
||||||
Future<Map<String, dynamic>> getPreviousMaintenanceLogs({
|
Future<Map<String, dynamic>> getPreviousMaintenanceLogs({
|
||||||
required AuthProvider authProvider,
|
required AuthProvider authProvider,
|
||||||
}) async {
|
}) async {
|
||||||
|
|||||||
@ -2,53 +2,254 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
|
||||||
import '../auth_provider.dart';
|
import '../auth_provider.dart';
|
||||||
import '../models/marine_manual_pre_departure_checklist_data.dart';
|
import '../models/marine_manual_pre_departure_checklist_data.dart';
|
||||||
import 'api_service.dart';
|
import 'api_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/database_helper.dart';
|
import 'package:environment_monitoring_app/services/database_helper.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/local_storage_service.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/server_config_service.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/retry_service.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/submission_api_service.dart';
|
||||||
|
|
||||||
import 'base_api_service.dart'; // Import for SessionExpiredException
|
import 'base_api_service.dart'; // Import for SessionExpiredException
|
||||||
|
|
||||||
class MarineManualPreDepartureService {
|
class MarineManualPreDepartureService {
|
||||||
final ApiService _apiService;
|
// Use the new generic submission service
|
||||||
|
final SubmissionApiService _submissionApiService = SubmissionApiService();
|
||||||
|
final LocalStorageService _localStorageService = LocalStorageService();
|
||||||
|
final ServerConfigService _serverConfigService = ServerConfigService();
|
||||||
|
final DatabaseHelper _dbHelper = DatabaseHelper();
|
||||||
|
final RetryService _retryService = RetryService();
|
||||||
|
|
||||||
MarineManualPreDepartureService(this._apiService);
|
// The ApiService is kept only if other non-submission methods need it.
|
||||||
|
// For this refactor, we'll remove it from the constructor.
|
||||||
|
// final ApiService _apiService;
|
||||||
|
// MarineManualPreDepartureService(this._apiService);
|
||||||
|
MarineManualPreDepartureService(ApiService apiService); // Keep constructor signature for main.dart
|
||||||
|
|
||||||
|
/// Main submission method with online/offline branching logic
|
||||||
Future<Map<String, dynamic>> submitChecklist({
|
Future<Map<String, dynamic>> submitChecklist({
|
||||||
required MarineManualPreDepartureChecklistData data,
|
required MarineManualPreDepartureChecklistData data,
|
||||||
required AuthProvider authProvider,
|
required AuthProvider authProvider,
|
||||||
|
List<Map<String, dynamic>>? appSettings, // Added for consistency
|
||||||
|
BuildContext? context, // Added for consistency
|
||||||
|
String? logDirectory,
|
||||||
}) async {
|
}) async {
|
||||||
|
const String moduleName = 'marine_pre_departure';
|
||||||
|
|
||||||
|
// --- START: ADDED LINE ---
|
||||||
|
// Populate the user name from the AuthProvider
|
||||||
|
data.reporterName = authProvider.profileData?['first_name'] as String?;
|
||||||
|
// --- END: ADDED LINE ---
|
||||||
|
|
||||||
|
final connectivityResult = await Connectivity().checkConnectivity();
|
||||||
|
bool isOnline = connectivityResult != ConnectivityResult.none;
|
||||||
|
bool isOfflineSession = authProvider.isLoggedIn &&
|
||||||
|
(authProvider.profileData?['token']
|
||||||
|
?.startsWith("offline-session-") ??
|
||||||
|
false);
|
||||||
|
|
||||||
|
if (isOnline && isOfflineSession) {
|
||||||
|
debugPrint(
|
||||||
|
"$moduleName submission online during offline session. Attempting auto-relogin...");
|
||||||
|
final bool transitionSuccess =
|
||||||
|
await authProvider.checkAndTransitionToOnlineSession();
|
||||||
|
if (transitionSuccess) {
|
||||||
|
isOfflineSession = false;
|
||||||
|
} else {
|
||||||
|
isOnline = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOnline && !isOfflineSession) {
|
||||||
|
debugPrint("Proceeding with direct ONLINE $moduleName submission...");
|
||||||
|
return await _performOnlineSubmission(
|
||||||
|
data: data,
|
||||||
|
moduleName: moduleName,
|
||||||
|
authProvider: authProvider,
|
||||||
|
logDirectory: logDirectory,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
debugPrint("Proceeding with OFFLINE $moduleName queuing mechanism...");
|
||||||
|
return await _performOfflineQueuing(
|
||||||
|
data: data,
|
||||||
|
moduleName: moduleName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles the direct online submission.
|
||||||
|
Future<Map<String, dynamic>> _performOnlineSubmission({
|
||||||
|
required MarineManualPreDepartureChecklistData data,
|
||||||
|
required String moduleName,
|
||||||
|
required AuthProvider authProvider,
|
||||||
|
String? logDirectory,
|
||||||
|
}) async {
|
||||||
|
final serverName =
|
||||||
|
(await _serverConfigService.getActiveApiConfig())?['config_name']
|
||||||
|
as String? ??
|
||||||
|
'Default';
|
||||||
|
Map<String, dynamic> apiResult;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call the existing method in MarineApiService
|
apiResult = await _submissionApiService.submitPost(
|
||||||
return await _apiService.marine.submitPreDepartureChecklist(data);
|
moduleName: moduleName,
|
||||||
|
endpoint: 'marine/checklist', // Endpoint from marine_api_service.dart
|
||||||
|
body: data.toApiFormData(),
|
||||||
|
);
|
||||||
} on SessionExpiredException {
|
} on SessionExpiredException {
|
||||||
// Handle session expiry by attempting a silent relogin
|
|
||||||
final bool reloginSuccess = await authProvider.attemptSilentRelogin();
|
final bool reloginSuccess = await authProvider.attemptSilentRelogin();
|
||||||
if (reloginSuccess) {
|
if (reloginSuccess) {
|
||||||
// Retry the submission once if relogin was successful
|
apiResult = await _submissionApiService.submitPost(
|
||||||
return await _apiService.marine.submitPreDepartureChecklist(data);
|
moduleName: moduleName,
|
||||||
|
endpoint: 'marine/checklist',
|
||||||
|
body: data.toApiFormData(),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return {
|
apiResult = {
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Session expired. Please log in again.'
|
'message': 'Session expired. Please log in again.'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} on SocketException {
|
} on SocketException catch (e) {
|
||||||
// Handle network errors
|
apiResult = {
|
||||||
return {
|
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Submission failed. Please check your network connection.'
|
'message': "API submission failed with network error: $e"
|
||||||
};
|
};
|
||||||
} on TimeoutException {
|
// submission_api_service will queue this failure
|
||||||
// Handle timeout errors
|
} on TimeoutException catch (e) {
|
||||||
return {
|
apiResult = {
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Submission timed out. Please check your network connection.'
|
'message': "API submission timed out: $e"
|
||||||
};
|
};
|
||||||
|
// submission_api_service will queue this failure
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Handle any other unexpected errors
|
apiResult = {
|
||||||
return {'success': false, 'message': 'An unexpected error occurred: $e'};
|
'success': false,
|
||||||
|
'message': 'An unexpected error occurred: $e'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log the final result
|
||||||
|
final bool overallSuccess = apiResult['success'] == true;
|
||||||
|
final String finalMessage =
|
||||||
|
apiResult['message'] ?? (overallSuccess ? 'Submission successful.' : 'Submission failed.');
|
||||||
|
final String finalStatus = overallSuccess ? 'S4' : 'L1'; // S4 = API Success
|
||||||
|
|
||||||
|
if (overallSuccess) {
|
||||||
|
// Assuming the API returns an ID. Adjust 'checklist_id' if needed.
|
||||||
|
data.reportId = apiResult['data']?['checklist_id']?.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _logAndSave(
|
||||||
|
data: data,
|
||||||
|
status: finalStatus,
|
||||||
|
message: finalMessage,
|
||||||
|
apiResult: apiResult,
|
||||||
|
serverName: serverName,
|
||||||
|
logDirectory: logDirectory,
|
||||||
|
);
|
||||||
|
|
||||||
|
return apiResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles saving the submission to local storage and queuing for retry.
|
||||||
|
Future<Map<String, dynamic>> _performOfflineQueuing({
|
||||||
|
required MarineManualPreDepartureChecklistData data,
|
||||||
|
required String moduleName,
|
||||||
|
}) async {
|
||||||
|
final serverConfig = await _serverConfigService.getActiveApiConfig();
|
||||||
|
final serverName =
|
||||||
|
serverConfig?['config_name'] as String? ?? 'Default';
|
||||||
|
|
||||||
|
data.submissionStatus = 'L1';
|
||||||
|
data.submissionMessage = 'Pre-Departure Checklist queued due to being offline.';
|
||||||
|
|
||||||
|
// This method is added to LocalStorageService
|
||||||
|
final String? localLogPath =
|
||||||
|
await _localStorageService.savePreDepartureData(data, serverName: serverName);
|
||||||
|
|
||||||
|
if (localLogPath == null) {
|
||||||
|
const message =
|
||||||
|
"Failed to save Pre-Departure Checklist to local device storage.";
|
||||||
|
await _logAndSave(
|
||||||
|
data: data,
|
||||||
|
status: 'Error',
|
||||||
|
message: message,
|
||||||
|
apiResult: {},
|
||||||
|
serverName: serverName);
|
||||||
|
return {'success': false, 'message': message};
|
||||||
|
}
|
||||||
|
|
||||||
|
await _retryService.queueTask(
|
||||||
|
type: 'pre_departure_submission', // New task type
|
||||||
|
payload: {
|
||||||
|
'module': moduleName,
|
||||||
|
'localLogPath': localLogPath,
|
||||||
|
'serverConfig': serverConfig,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const successMessage =
|
||||||
|
"No internet connection. Pre-Departure Checklist has been saved and queued for upload.";
|
||||||
|
return {'success': true, 'message': successMessage};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs the submission to the local file system and the central SQL database.
|
||||||
|
Future<void> _logAndSave({
|
||||||
|
required MarineManualPreDepartureChecklistData data,
|
||||||
|
required String status,
|
||||||
|
required String message,
|
||||||
|
required Map<String, dynamic> apiResult,
|
||||||
|
required String serverName,
|
||||||
|
String? logDirectory,
|
||||||
|
}) async {
|
||||||
|
data.submissionStatus = status;
|
||||||
|
data.submissionMessage = message;
|
||||||
|
|
||||||
|
final fileTimestamp = data.submissionDate ?? DateTime.now().toIso8601String();
|
||||||
|
|
||||||
|
if (logDirectory != null) {
|
||||||
|
// This is an update to an existing log file
|
||||||
|
// --- START: MODIFIED BLOCK ---
|
||||||
|
final Map<String, dynamic> updatedLogData = data.toDbJson();
|
||||||
|
// Add metadata
|
||||||
|
updatedLogData['submissionStatus'] = status;
|
||||||
|
updatedLogData['submissionMessage'] = message;
|
||||||
|
updatedLogData['logDirectory'] = logDirectory;
|
||||||
|
updatedLogData['serverConfigName'] = serverName;
|
||||||
|
updatedLogData['api_status'] = jsonEncode(apiResult);
|
||||||
|
// All other fields are now in toDbJson()
|
||||||
|
// --- END: MODIFIED BLOCK ---
|
||||||
|
|
||||||
|
// This method is added to LocalStorageService
|
||||||
|
await _localStorageService.updatePreDepartureLog(updatedLogData);
|
||||||
|
} else {
|
||||||
|
// This is a new log
|
||||||
|
// This method is added to LocalStorageService
|
||||||
|
await _localStorageService.savePreDepartureData(data, serverName: serverName);
|
||||||
|
}
|
||||||
|
|
||||||
|
final logData = {
|
||||||
|
'submission_id': data.reportId ?? fileTimestamp,
|
||||||
|
'module': 'marine',
|
||||||
|
'type': 'Pre-Departure Checklist',
|
||||||
|
'status': data.submissionStatus,
|
||||||
|
'message': data.submissionMessage,
|
||||||
|
'report_id': data.reportId,
|
||||||
|
'created_at': DateTime.now().toIso8601String(),
|
||||||
|
// --- START: MODIFIED LINE ---
|
||||||
|
'form_data': jsonEncode(data.toDbJson()), // Log the full DbJson
|
||||||
|
// --- END: MODIFIED LINE ---
|
||||||
|
'image_data': null, // No images
|
||||||
|
'server_name': serverName,
|
||||||
|
'api_status': jsonEncode(apiResult),
|
||||||
|
'ftp_status': null, // No FTP
|
||||||
|
};
|
||||||
|
await _dbHelper.saveSubmissionLog(logData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,53 +2,250 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
|
||||||
import '../auth_provider.dart';
|
import '../auth_provider.dart';
|
||||||
import '../models/marine_manual_sonde_calibration_data.dart';
|
import '../models/marine_manual_sonde_calibration_data.dart';
|
||||||
import 'api_service.dart';
|
import 'api_service.dart';
|
||||||
import 'package:environment_monitoring_app/services/database_helper.dart';
|
import 'package:environment_monitoring_app/services/database_helper.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/local_storage_service.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/server_config_service.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/retry_service.dart';
|
||||||
|
import 'package:environment_monitoring_app/services/submission_api_service.dart';
|
||||||
|
|
||||||
import 'base_api_service.dart'; // Import for SessionExpiredException
|
import 'base_api_service.dart'; // Import for SessionExpiredException
|
||||||
|
|
||||||
class MarineManualSondeCalibrationService {
|
class MarineManualSondeCalibrationService {
|
||||||
final ApiService _apiService;
|
// Use the new generic submission service
|
||||||
|
final SubmissionApiService _submissionApiService = SubmissionApiService();
|
||||||
|
final LocalStorageService _localStorageService = LocalStorageService();
|
||||||
|
final ServerConfigService _serverConfigService = ServerConfigService();
|
||||||
|
final DatabaseHelper _dbHelper = DatabaseHelper();
|
||||||
|
final RetryService _retryService = RetryService();
|
||||||
|
|
||||||
MarineManualSondeCalibrationService(this._apiService);
|
// The ApiService is kept only if other non-submission methods need it.
|
||||||
|
// For this refactor, we'll remove it from the constructor.
|
||||||
|
// final ApiService _apiService;
|
||||||
|
// MarineManualSondeCalibrationService(this._apiService);
|
||||||
|
MarineManualSondeCalibrationService(ApiService apiService); // Keep constructor signature for main.dart
|
||||||
|
|
||||||
|
/// Main submission method with online/offline branching logic
|
||||||
Future<Map<String, dynamic>> submitCalibration({
|
Future<Map<String, dynamic>> submitCalibration({
|
||||||
required MarineManualSondeCalibrationData data,
|
required MarineManualSondeCalibrationData data,
|
||||||
required AuthProvider authProvider,
|
required AuthProvider authProvider,
|
||||||
|
List<Map<String, dynamic>>? appSettings, // Added for consistency
|
||||||
|
BuildContext? context, // Added for consistency
|
||||||
|
String? logDirectory,
|
||||||
}) async {
|
}) async {
|
||||||
|
const String moduleName = 'marine_sonde_calibration';
|
||||||
|
|
||||||
|
// --- START: ADDED LINE ---
|
||||||
|
// Populate the user name from the AuthProvider
|
||||||
|
data.calibratedByUserName = authProvider.profileData?['first_name'] as String?;
|
||||||
|
// --- END: ADDED LINE ---
|
||||||
|
|
||||||
|
final connectivityResult = await Connectivity().checkConnectivity();
|
||||||
|
bool isOnline = connectivityResult != ConnectivityResult.none;
|
||||||
|
bool isOfflineSession = authProvider.isLoggedIn &&
|
||||||
|
(authProvider.profileData?['token']
|
||||||
|
?.startsWith("offline-session-") ??
|
||||||
|
false);
|
||||||
|
|
||||||
|
if (isOnline && isOfflineSession) {
|
||||||
|
debugPrint(
|
||||||
|
"$moduleName submission online during offline session. Attempting auto-relogin...");
|
||||||
|
final bool transitionSuccess =
|
||||||
|
await authProvider.checkAndTransitionToOnlineSession();
|
||||||
|
if (transitionSuccess) {
|
||||||
|
isOfflineSession = false;
|
||||||
|
} else {
|
||||||
|
isOnline = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOnline && !isOfflineSession) {
|
||||||
|
debugPrint("Proceeding with direct ONLINE $moduleName submission...");
|
||||||
|
return await _performOnlineSubmission(
|
||||||
|
data: data,
|
||||||
|
moduleName: moduleName,
|
||||||
|
authProvider: authProvider,
|
||||||
|
logDirectory: logDirectory,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
debugPrint("Proceeding with OFFLINE $moduleName queuing mechanism...");
|
||||||
|
return await _performOfflineQueuing(
|
||||||
|
data: data,
|
||||||
|
moduleName: moduleName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles the direct online submission.
|
||||||
|
Future<Map<String, dynamic>> _performOnlineSubmission({
|
||||||
|
required MarineManualSondeCalibrationData data,
|
||||||
|
required String moduleName,
|
||||||
|
required AuthProvider authProvider,
|
||||||
|
String? logDirectory,
|
||||||
|
}) async {
|
||||||
|
final serverName =
|
||||||
|
(await _serverConfigService.getActiveApiConfig())?['config_name']
|
||||||
|
as String? ??
|
||||||
|
'Default';
|
||||||
|
Map<String, dynamic> apiResult;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call the existing method in MarineApiService
|
apiResult = await _submissionApiService.submitPost(
|
||||||
return await _apiService.marine.submitSondeCalibration(data);
|
moduleName: moduleName,
|
||||||
|
endpoint: 'marine/calibration', // Endpoint from marine_api_service.dart
|
||||||
|
body: data.toApiFormData(),
|
||||||
|
);
|
||||||
} on SessionExpiredException {
|
} on SessionExpiredException {
|
||||||
// Handle session expiry by attempting a silent relogin
|
|
||||||
final bool reloginSuccess = await authProvider.attemptSilentRelogin();
|
final bool reloginSuccess = await authProvider.attemptSilentRelogin();
|
||||||
if (reloginSuccess) {
|
if (reloginSuccess) {
|
||||||
// Retry the submission once if relogin was successful
|
apiResult = await _submissionApiService.submitPost(
|
||||||
return await _apiService.marine.submitSondeCalibration(data);
|
moduleName: moduleName,
|
||||||
|
endpoint: 'marine/calibration',
|
||||||
|
body: data.toApiFormData(),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return {
|
apiResult = {
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Session expired. Please log in again.'
|
'message': 'Session expired. Please log in again.'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} on SocketException {
|
} on SocketException catch (e) {
|
||||||
// Handle network errors
|
apiResult = {
|
||||||
return {
|
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Submission failed. Please check your network connection.'
|
'message': "API submission failed with network error: $e"
|
||||||
};
|
};
|
||||||
} on TimeoutException {
|
// submission_api_service will queue this failure
|
||||||
// Handle timeout errors
|
} on TimeoutException catch (e) {
|
||||||
return {
|
apiResult = {
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Submission timed out. Please check your network connection.'
|
'message': "API submission timed out: $e"
|
||||||
};
|
};
|
||||||
|
// submission_api_service will queue this failure
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Handle any other unexpected errors
|
apiResult = {
|
||||||
return {'success': false, 'message': 'An unexpected error occurred: $e'};
|
'success': false,
|
||||||
|
'message': 'An unexpected error occurred: $e'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log the final result
|
||||||
|
final bool overallSuccess = apiResult['success'] == true;
|
||||||
|
final String finalMessage =
|
||||||
|
apiResult['message'] ?? (overallSuccess ? 'Submission successful.' : 'Submission failed.');
|
||||||
|
final String finalStatus = overallSuccess ? 'S4' : 'L1'; // S4 = API Success
|
||||||
|
|
||||||
|
if (overallSuccess) {
|
||||||
|
// Assuming the API returns an ID. Adjust 'calibration_id' if needed.
|
||||||
|
data.reportId = apiResult['data']?['calibration_id']?.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _logAndSave(
|
||||||
|
data: data,
|
||||||
|
status: finalStatus,
|
||||||
|
message: finalMessage,
|
||||||
|
apiResult: apiResult,
|
||||||
|
serverName: serverName,
|
||||||
|
logDirectory: logDirectory,
|
||||||
|
);
|
||||||
|
|
||||||
|
return apiResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles saving the submission to local storage and queuing for retry.
|
||||||
|
Future<Map<String, dynamic>> _performOfflineQueuing({
|
||||||
|
required MarineManualSondeCalibrationData data,
|
||||||
|
required String moduleName,
|
||||||
|
}) async {
|
||||||
|
final serverConfig = await _serverConfigService.getActiveApiConfig();
|
||||||
|
final serverName =
|
||||||
|
serverConfig?['config_name'] as String? ?? 'Default';
|
||||||
|
|
||||||
|
data.submissionStatus = 'L1';
|
||||||
|
data.submissionMessage = 'Sonde Calibration queued due to being offline.';
|
||||||
|
|
||||||
|
// This method is added to LocalStorageService
|
||||||
|
final String? localLogPath =
|
||||||
|
await _localStorageService.saveSondeCalibrationData(data, serverName: serverName);
|
||||||
|
|
||||||
|
if (localLogPath == null) {
|
||||||
|
const message =
|
||||||
|
"Failed to save Sonde Calibration to local device storage.";
|
||||||
|
await _logAndSave(
|
||||||
|
data: data,
|
||||||
|
status: 'Error',
|
||||||
|
message: message,
|
||||||
|
apiResult: {},
|
||||||
|
serverName: serverName);
|
||||||
|
return {'success': false, 'message': message};
|
||||||
|
}
|
||||||
|
|
||||||
|
await _retryService.queueTask(
|
||||||
|
type: 'sonde_calibration_submission', // New task type
|
||||||
|
payload: {
|
||||||
|
'module': moduleName,
|
||||||
|
'localLogPath': localLogPath,
|
||||||
|
'serverConfig': serverConfig,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const successMessage =
|
||||||
|
"No internet connection. Sonde Calibration has been saved and queued for upload.";
|
||||||
|
return {'success': true, 'message': successMessage};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs the submission to the local file system and the central SQL database.
|
||||||
|
Future<void> _logAndSave({
|
||||||
|
required MarineManualSondeCalibrationData data,
|
||||||
|
required String status,
|
||||||
|
required String message,
|
||||||
|
required Map<String, dynamic> apiResult,
|
||||||
|
required String serverName,
|
||||||
|
String? logDirectory,
|
||||||
|
}) async {
|
||||||
|
data.submissionStatus = status;
|
||||||
|
data.submissionMessage = message;
|
||||||
|
|
||||||
|
final fileTimestamp = data.startDateTime?.replaceAll(':', '-').replaceAll(' ', '_') ?? DateTime.now().toIso8601String();
|
||||||
|
|
||||||
|
// --- START: MODIFIED BLOCK ---
|
||||||
|
// Use the new toDbJson() method to get ALL data for logging
|
||||||
|
final Map<String, dynamic> logDataMap = data.toDbJson();
|
||||||
|
// Add submission-specific metadata
|
||||||
|
logDataMap['api_status'] = jsonEncode(apiResult);
|
||||||
|
logDataMap['serverConfigName'] = serverName;
|
||||||
|
// --- END: MODIFIED BLOCK ---
|
||||||
|
|
||||||
|
|
||||||
|
if (logDirectory != null) {
|
||||||
|
// This is an update to an existing log file
|
||||||
|
logDataMap['logDirectory'] = logDirectory; // Ensure logDirectory is in the map
|
||||||
|
await _localStorageService.updateSondeCalibrationLog(logDataMap);
|
||||||
|
} else {
|
||||||
|
// This is a new log
|
||||||
|
// Pass the complete data object, which now includes the user name
|
||||||
|
await _localStorageService.saveSondeCalibrationData(data, serverName: serverName);
|
||||||
|
}
|
||||||
|
|
||||||
|
final logData = {
|
||||||
|
'submission_id': data.reportId ?? fileTimestamp,
|
||||||
|
'module': 'marine',
|
||||||
|
'type': 'Sonde Calibration',
|
||||||
|
'status': data.submissionStatus,
|
||||||
|
'message': data.submissionMessage,
|
||||||
|
'report_id': data.reportId,
|
||||||
|
'created_at': DateTime.now().toIso8601String(),
|
||||||
|
'form_data': jsonEncode(data.toDbJson()), // <-- Use toDbJson here
|
||||||
|
'image_data': null, // No images
|
||||||
|
'server_name': serverName,
|
||||||
|
'api_status': jsonEncode(apiResult),
|
||||||
|
'ftp_status': null, // No FTP
|
||||||
|
};
|
||||||
|
await _dbHelper.saveSubmissionLog(logData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user