-
-
Save naiplawan/6ac7dd61159553a7df58927fdf2257fd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| import 'package:provider/provider.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/custom_form_field.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/loading_view.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/photo_widget.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/section_header.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/step_navigation.dart'; | |
| import 'package:pwa_ocs_flutter/ui/core/ui/main_scaffold.dart'; | |
| import 'package:pwa_ocs_flutter/ui/core/ui/low_pressure_water_meter_form_dialog.dart'; | |
| import 'package:pwa_ocs_flutter/ui/core/ui/low_pressure_water_pump_form_dialog.dart'; | |
| import 'package:pwa_ocs_flutter/features/p602/domain/entities/models/low_pressure_water.dart'; | |
| import 'package:pwa_ocs_flutter/features/p602/domain/entities/models/low_pressure_water_pump.dart'; | |
| import 'package:pwa_ocs_flutter/features/p600/presentation/bloc/p600_viewmodel.dart'; | |
| import 'package:pwa_ocs_flutter/features/p602/presentation/bloc/p602_viewmodel.dart'; | |
| import 'package:pwa_ocs_flutter/shared/constants/form_options.dart'; | |
| class P602 extends StatelessWidget { | |
| P602({super.key}); | |
| Future<void> _handleSubmit( | |
| BuildContext context, | |
| P602ViewModel controller, | |
| P600State args, | |
| ) async { | |
| final result = await controller.onSubmit(); | |
| if (!context.mounted) return; | |
| if (result == 'success') { | |
| showDialog( | |
| context: context, | |
| barrierDismissible: false, | |
| builder: (dialogContext) => AlertDialog( | |
| title: const Text('ส่งข้อมูลสำเร็จ', textAlign: TextAlign.center), | |
| content: Column( | |
| mainAxisSize: MainAxisSize.min, | |
| spacing: 16, | |
| children: const [ | |
| Icon(Icons.check_circle, size: 64, color: Colors.green), | |
| Text('กำลังกลับไปหน้ารายการ...'), | |
| ], | |
| ), | |
| ), | |
| ); | |
| await Future.delayed(const Duration(seconds: 2)); | |
| if (!context.mounted) return; | |
| Navigator.of(context).pushReplacementNamed('/600', arguments: args); | |
| } else if (result == 'invalid') { | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| SnackBar( | |
| content: Row( | |
| children: const [ | |
| Icon(Icons.warning_amber_rounded, color: Colors.white), | |
| SizedBox(width: 12), | |
| Expanded(child: Text('กรุณากรอกข้อมูลให้ครบทุกขั้นตอน')), | |
| ], | |
| ), | |
| backgroundColor: Colors.orange[600], | |
| behavior: SnackBarBehavior.floating, | |
| margin: const EdgeInsets.all(16), | |
| shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), | |
| duration: const Duration(seconds: 4), | |
| action: SnackBarAction( | |
| label: 'ปิด', | |
| textColor: Colors.white, | |
| onPressed: () { | |
| ScaffoldMessenger.of(context).hideCurrentSnackBar(); | |
| }, | |
| ), | |
| ), | |
| ); | |
| } else { | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| SnackBar( | |
| content: Row( | |
| spacing: 12, | |
| children: const [ | |
| Icon(Icons.error_outline, color: Colors.white), | |
| Expanded(child: Text('ส่งข้อมูลไม่สำเร็จ')), | |
| ], | |
| ), | |
| backgroundColor: Colors.red[600], | |
| behavior: SnackBarBehavior.floating, | |
| margin: const EdgeInsets.all(16), | |
| shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), | |
| action: SnackBarAction( | |
| label: 'ปิด', | |
| textColor: Colors.white, | |
| onPressed: () { | |
| ScaffoldMessenger.of(context).hideCurrentSnackBar(); | |
| }, | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| @override | |
| Widget build(context) { | |
| final args = ModalRoute.of(context)!.settings.arguments as P600State; | |
| return ChangeNotifierProvider<P602ViewModel>( | |
| create: (context) => P602ViewModel( | |
| args.item.plantId, | |
| args.item.operationDateTimestamp.split('T')[0], | |
| args.item.workScheduleId, | |
| initialStep: args.item.submanu[args.index].activitySeq, | |
| ), | |
| child: Consumer<P602ViewModel>( | |
| builder: (context, controller, child) { | |
| if (controller.isLoading) { | |
| return MainScaffold( | |
| title: args.item.plantName, | |
| subtitle: args.item.operationDate, | |
| child: const LoadingView(), | |
| ); | |
| } else if (controller.error != null) { | |
| return MainScaffold( | |
| title: args.item.plantName, | |
| subtitle: args.item.operationDate, | |
| child: Center( | |
| child: Padding( | |
| padding: const EdgeInsets.all(16), | |
| child: Text('Error: ${controller.error}'), | |
| ), | |
| ), | |
| ); | |
| } | |
| final items = [ | |
| _buildBase(controller), | |
| if (controller.lowPressureId != null) ...[ | |
| if (controller.currentStep == 1) ..._buildStep1(controller, args), | |
| if (controller.currentStep == 2) | |
| ..._buildStep2(context, controller, args), | |
| if (controller.currentStep == 3) | |
| ..._buildStep3(context, controller, args), | |
| ], | |
| ]; | |
| return MainScaffold( | |
| title: args.item.plantName, | |
| subtitle: args.item.operationDate, | |
| child: Form( | |
| key: controller.key, | |
| child: Container( | |
| padding: EdgeInsets.symmetric(vertical: 16.0), | |
| child: Column( | |
| spacing: 16.0, | |
| children: [ | |
| Expanded( | |
| child: RefreshIndicator( | |
| onRefresh: controller.fetchAndUpdateCache, | |
| child: ListView.separated( | |
| padding: EdgeInsets.symmetric( | |
| vertical: 24, | |
| horizontal: 16, | |
| ), | |
| itemCount: items.length, | |
| itemBuilder: (context, index) => items[index], | |
| separatorBuilder: (context, index) => | |
| SizedBox(height: 24.0), | |
| ), | |
| ), | |
| ), | |
| StepNavigator( | |
| onNext: controller.next, | |
| current: controller.currentStep, | |
| onPrevious: controller.prev, | |
| total: P602ViewModel.MAX_STEP, | |
| submitting: controller.isSubmitting, | |
| submitable: controller.isFormValid(), | |
| onSubmit: () => _handleSubmit(context, controller, args), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| }, | |
| ), | |
| ); | |
| } | |
| Widget _buildBase(P602ViewModel controller) => CustomDropdownField( | |
| label: "เลือกสถานี", | |
| key: Key('low_pressure_id'), | |
| value: controller.lowPressureId, | |
| onChanged: controller.selectLowPressureWater, | |
| items: controller.lowPressureWaterList | |
| .map( | |
| (option) => DropdownMenuItem( | |
| child: Text(option.lowPressureName), | |
| value: option.lowPressureId, | |
| ), | |
| ) | |
| .toList(), | |
| ); | |
| List<Widget> _buildStep1(P602ViewModel controller, P600State args) => [ | |
| SectionHeader(title: _activityTitle(args, 0)), | |
| CustomSelectorField( | |
| key: Key('cleanliness_status'), | |
| initialValue: controller.cleanlinessStatus, | |
| enabled: controller.lowPressureId != null, | |
| onSelectionChanged: controller.selectCleanlinessStatus, | |
| options: normalAbnormalCodedOptions, | |
| ), | |
| if (controller.cleanlinessStatus == "I") ...[ | |
| CustomFormField( | |
| label: "โปรดระบุ", | |
| key: Key('cleanliness_remark'), | |
| enabled: controller.lowPressureId != null, | |
| controller: controller.cleanlinessRemark, | |
| ), | |
| ], | |
| PhotoUploadWidget( | |
| title: "ถ่ายรูปสภาพแวดล้อม", | |
| maxPhotos: 5, | |
| photos: controller.environmentPhotos, | |
| onPhotoTaken: controller.addEnvironmentPhoto, | |
| onRemovePhoto: controller.removeEnvironmentPhoto, | |
| ), | |
| ]; | |
| List<Widget> _buildStep2( | |
| BuildContext context, | |
| P602ViewModel controller, | |
| P600State args, | |
| ) { | |
| final selected = _findSelectedLowPressure(controller); | |
| return [ | |
| SectionHeader(title: _activityTitle(args, 1)), | |
| if (selected == null) | |
| const Text('ไม่พบข้อมูลสถานีที่เลือก') | |
| else | |
| Wrap( | |
| runSpacing: 16, | |
| children: selected.waterPump.asMap().entries.map((entry) { | |
| final index = entry.key; | |
| final pump = entry.value; | |
| return SizedBox( | |
| width: (MediaQuery.of(context).size.width - 32) / 4, | |
| child: TextButton( | |
| style: TextButton.styleFrom( | |
| shape: RoundedRectangleBorder( | |
| borderRadius: BorderRadius.circular(16), | |
| ), | |
| ), | |
| onPressed: () { | |
| final initialPressureWaterPump = controller | |
| .getDraftLowPressureWaterPump(pump); | |
| showDialog( | |
| context: context, | |
| builder: (dialogContext) { | |
| final formKey = GlobalKey<FormState>(); | |
| return LowPressureWaterPumpFormDialog( | |
| formKey: formKey, | |
| pressureWaterPump: initialPressureWaterPump, | |
| onSubmit: (lowPressurePump) async { | |
| try { | |
| // Show loading indicator | |
| showDialog( | |
| context: dialogContext, | |
| barrierDismissible: false, | |
| builder: (_) => const Center( | |
| child: CircularProgressIndicator(), | |
| ), | |
| ); | |
| // Call API to update pump | |
| await controller.updateLowPressureWaterPump( | |
| index, | |
| lowPressurePump, | |
| ); | |
| // Close loading indicator | |
| if (dialogContext.mounted) { | |
| Navigator.of(dialogContext).pop(); | |
| } | |
| // Show success message | |
| if (dialogContext.mounted) { | |
| ScaffoldMessenger.of(dialogContext).showSnackBar( | |
| const SnackBar( | |
| content: Text('บันทึกข้อมูลเครื่องสูบสำเร็จ'), | |
| backgroundColor: Colors.green, | |
| duration: Duration(seconds: 2), | |
| ), | |
| ); | |
| } | |
| } catch (e) { | |
| // Close loading indicator | |
| if (dialogContext.mounted) { | |
| Navigator.of(dialogContext).pop(); | |
| } | |
| // Show error message | |
| if (dialogContext.mounted) { | |
| ScaffoldMessenger.of(dialogContext).showSnackBar( | |
| SnackBar( | |
| content: Text( | |
| 'เกิดข้อผิดพลาด: ${e.toString()}', | |
| ), | |
| backgroundColor: Colors.red, | |
| duration: const Duration(seconds: 3), | |
| ), | |
| ); | |
| } | |
| } | |
| }, | |
| ); | |
| }, | |
| ); | |
| }, | |
| child: Column( | |
| children: [ | |
| Image.asset( | |
| _getPumpImagePath(pump), | |
| width: 64, | |
| height: 64, | |
| fit: BoxFit.contain, | |
| alignment: Alignment.center, | |
| ), | |
| SizedBox(height: 8), | |
| Text( | |
| pump.waterPumpName ?? '', | |
| style: TextStyle( | |
| fontSize: 14, | |
| fontWeight: FontWeight.w500, | |
| height: 1.4, | |
| color: Color(0xFF191C20), | |
| ), | |
| textAlign: TextAlign.center, | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| }).toList(), | |
| ), | |
| ]; | |
| } | |
| List<Widget> _buildStep3( | |
| BuildContext context, | |
| P602ViewModel controller, | |
| P600State args, | |
| ) { | |
| final selected = _findSelectedLowPressure(controller); | |
| return [ | |
| SectionHeader(title: _activityTitle(args, 2)), | |
| if (selected == null) | |
| const Text('ไม่พบข้อมูลสถานีที่เลือก') | |
| else | |
| Wrap( | |
| runSpacing: 16, | |
| children: selected.waterMeter.map((meter) { | |
| return SizedBox( | |
| width: (MediaQuery.of(context).size.width - 32) / 4, | |
| child: TextButton( | |
| style: TextButton.styleFrom( | |
| shape: RoundedRectangleBorder( | |
| borderRadius: BorderRadius.circular(16), | |
| ), | |
| ), | |
| onPressed: () { | |
| final initialLowPressureWaterMeter = controller | |
| .getDraftLowPressureWaterMeter(meter); | |
| showDialog( | |
| context: context, | |
| builder: (dialogContext) { | |
| final formKey = GlobalKey<FormState>(); | |
| return LowPressureWaterMeterFormDialog( | |
| formKey: formKey, | |
| pressureWaterMeter: initialLowPressureWaterMeter, | |
| onSubmit: (lowPressureMeter) async { | |
| try { | |
| // Show loading indicator | |
| showDialog( | |
| context: dialogContext, | |
| barrierDismissible: false, | |
| builder: (_) => const Center( | |
| child: CircularProgressIndicator(), | |
| ), | |
| ); | |
| // Call API to update meter | |
| await controller.updateMeter(lowPressureMeter); | |
| // Close loading indicator | |
| if (dialogContext.mounted) { | |
| Navigator.of(dialogContext).pop(); | |
| } | |
| // Show success message | |
| if (dialogContext.mounted) { | |
| ScaffoldMessenger.of(dialogContext).showSnackBar( | |
| const SnackBar( | |
| content: Text('บันทึกข้อมูลมาตรวัดน้ำสำเร็จ'), | |
| backgroundColor: Colors.green, | |
| duration: Duration(seconds: 2), | |
| ), | |
| ); | |
| } | |
| } catch (e) { | |
| // Close loading indicator | |
| if (dialogContext.mounted) { | |
| Navigator.of(dialogContext).pop(); | |
| } | |
| // Show error message | |
| if (dialogContext.mounted) { | |
| ScaffoldMessenger.of(dialogContext).showSnackBar( | |
| SnackBar( | |
| content: Text( | |
| 'เกิดข้อผิดพลาด: ${e.toString()}', | |
| ), | |
| backgroundColor: Colors.red, | |
| duration: const Duration(seconds: 3), | |
| ), | |
| ); | |
| } | |
| } | |
| }, | |
| ); | |
| }, | |
| ); | |
| }, | |
| child: Column( | |
| children: [ | |
| Image.asset( | |
| 'images/raw-water-meter${meter.rawWaterMeterStatus != null && meter.rawWaterMeterStatus!.isNotEmpty ? '-${meter.rawWaterMeterStatus!.toLowerCase()}' : ""}.png', | |
| width: 64, | |
| height: 64, | |
| fit: BoxFit.contain, | |
| alignment: Alignment.center, | |
| ), | |
| SizedBox(height: 8), | |
| Text( | |
| meter.rawWaterMeterName ?? '', | |
| style: TextStyle( | |
| fontSize: 14, | |
| fontWeight: FontWeight.w500, | |
| height: 1.4, | |
| color: Color(0xFF191C20), | |
| ), | |
| textAlign: TextAlign.center, | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| }).toList(), | |
| ), | |
| ]; | |
| } | |
| String _activityTitle(P600State args, int index) { | |
| if (index >= 0 && index < args.item.submanu.length) { | |
| final title = args.item.submanu[index].activityName; | |
| return title; | |
| } | |
| return 'ไม่ระบุหัวข้อ'; | |
| } | |
| LowPressureWater? _findSelectedLowPressure(P602ViewModel controller) { | |
| for (final item in controller.lowPressureWaterList) { | |
| if (item.lowPressureId == controller.lowPressureId) { | |
| return item; | |
| } | |
| } | |
| return null; | |
| } | |
| /// Get the correct image asset path for water pump based on status | |
| /// Logic (Priority order): | |
| /// 1. machineCondition = "1" → raw-water-pump-1.png (พักเครื่อง) | |
| /// 2. machineCondition = "2" → raw-water-pump-2.png (สำรอง) | |
| /// 3. machineCondition = "3" → raw-water-pump-3.png (ใช้งานไม่ได้) | |
| /// 4. machineCondition = "4" → raw-water-pump-4.png (แจ้งซ่อม) | |
| /// 5. machinePowerStatus = "A" → Red (ใช้งาน) | |
| /// 6. Default → Blue (ไม่มีข้อมูล) | |
| String _getPumpImagePath(LowPressureWaterPump pump) { | |
| // Priority 1: Check machinePowerStatus = "A" → Red (Active/Running) | |
| if (pump.machinePowerStatus == 'A') { | |
| return 'images/raw-water-pump-a.png'; // ใช้งาน - สีแดง | |
| } | |
| // Priority 2: Check machineCondition for specific image | |
| if (pump.machineCondition != null && pump.machineCondition!.isNotEmpty) { | |
| final status = pump.machineCondition!; | |
| if (status == '1') { | |
| return 'images/raw-water-pump-1.png'; // พักเครื่อง - สีเขียว | |
| } else if (status == '2') { | |
| return 'images/raw-water-pump-2.png'; // สำรอง - สีเขียว | |
| } else if (status == '3') { | |
| return 'images/raw-water-pump-3.png'; // ใช้งานไม่ได้ - สีเทา | |
| } else if (status == '4') { | |
| return 'images/raw-water-pump-4.png'; // แจ้งซ่อม - สีเทา | |
| } | |
| } | |
| // Default: Blue - ไม่มีข้อมูล | |
| return 'images/raw-water-pump-i.png'; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| import 'package:provider/provider.dart'; | |
| import 'package:pwa_ocs_flutter/core/services/status_mapping_service.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/custom_form_field.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/loading_view.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/photo_widget.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/section_header.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/step_navigation.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/p607_filter_update_modal.dart'; | |
| import 'package:pwa_ocs_flutter/ui/core/ui/main_scaffold.dart'; | |
| import 'package:pwa_ocs_flutter/features/p600/presentation/bloc/p600_viewmodel.dart'; | |
| import 'package:pwa_ocs_flutter/features/p607/presentation/bloc/p607_viewmodel.dart'; | |
| class P607 extends StatelessWidget { | |
| P607({super.key}); | |
| @override | |
| Widget build(context) { | |
| final args = ModalRoute.of(context)?.settings.arguments as P600State?; | |
| if (args == null) { | |
| return const Scaffold( | |
| body: Center(child: Text('Error: Missing required arguments')), | |
| ); | |
| } | |
| return ChangeNotifierProvider<P607ViewModel>( | |
| create: (context) => P607ViewModel( | |
| plantId: args.item.plantId, | |
| operationDate: args.item.operationDateTimestamp.split('T')[0], | |
| workScheduleId: args.item.workScheduleId, | |
| initialStep: args.item.submanu[args.index].activitySeq, | |
| ), | |
| child: Consumer<P607ViewModel>( | |
| builder: (context, controller, child) { | |
| if (controller.isLoading) { | |
| return MainScaffold( | |
| title: args.item.plantName, | |
| subtitle: args.item.operationDate, | |
| child: const LoadingView(), | |
| ); | |
| } | |
| if (controller.error != null) { | |
| return MainScaffold( | |
| title: args.item.plantName, | |
| subtitle: args.item.operationDate, | |
| child: Center( | |
| child: Padding( | |
| padding: const EdgeInsets.all(16), | |
| child: Text(controller.error!), | |
| ), | |
| ), | |
| ); | |
| } | |
| final items = [ | |
| ..._buildBase(controller), | |
| if (controller.selectedPlant != null) ...[ | |
| if (controller.step == 1) ..._buildStep11(controller, args), | |
| if (controller.step == 2) ..._buildStep12(controller, args), | |
| if (controller.step == 3) | |
| ..._buildStep2(context, controller, args), | |
| if (controller.step == 4) ..._buildStep3(controller, args), | |
| if (controller.step == 5) ..._buildStep4(controller, args), | |
| ], | |
| ]; | |
| return MainScaffold( | |
| title: args.item.plantName, | |
| subtitle: args.item.operationDate, | |
| child: Form( | |
| key: controller.key, | |
| child: Container( | |
| padding: EdgeInsets.symmetric(vertical: 16.0), | |
| child: Column( | |
| spacing: 16.0, | |
| children: [ | |
| Expanded( | |
| child: RefreshIndicator( | |
| onRefresh: controller.fetchAndUpdateCache, | |
| child: ListView.separated( | |
| padding: EdgeInsets.symmetric( | |
| vertical: 24, | |
| horizontal: 16, | |
| ), | |
| itemCount: items.length, | |
| itemBuilder: (context, index) => items[index], | |
| separatorBuilder: (context, index) => | |
| SizedBox(height: 24.0), | |
| ), | |
| ), | |
| ), | |
| Builder( | |
| builder: (context) => StepNavigator( | |
| total: P607ViewModel.MAX_STEP, | |
| submitable: controller.hasPlants, | |
| submitting: controller.isSubmitting, | |
| onNext: () { | |
| // Navigation doesn't typically need error handling | |
| controller.next(); | |
| }, | |
| current: controller.step, | |
| onPrevious: () { | |
| // Navigation doesn't typically need error handling | |
| controller.prev(); | |
| }, | |
| onSubmit: () async { | |
| await controller.executeWithErrorHandling( | |
| context, | |
| () async { | |
| // Submit the form data to the server | |
| await controller.submitForm(); | |
| // Show success dialog | |
| if (context.mounted) { | |
| showDialog( | |
| context: context, | |
| barrierDismissible: false, | |
| builder: (dialogContext) => const AlertDialog( | |
| title: Text( | |
| 'ส่งข้อมูลสำเร็จ', | |
| textAlign: TextAlign.center, | |
| ), | |
| content: Column( | |
| mainAxisSize: MainAxisSize.min, | |
| spacing: 16, | |
| children: [ | |
| Icon( | |
| Icons.check_circle, | |
| size: 64, | |
| color: Colors.green, | |
| ), | |
| Text('กำลังกลับไปหน้ารายการ...'), | |
| ], | |
| ), | |
| ), | |
| ); | |
| // Wait 2 seconds before navigating back | |
| await Future.delayed( | |
| const Duration(seconds: 2), | |
| ); | |
| if (context.mounted) { | |
| Navigator.of(context).pushReplacementNamed( | |
| '/600', | |
| arguments: args, | |
| ); | |
| } | |
| } | |
| }, | |
| operationContext: 'P607.submit', | |
| ); | |
| }, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| }, | |
| ), | |
| ); | |
| } | |
| List<Widget> _buildBase(P607ViewModel controller) => [ | |
| Builder( | |
| builder: (context) { | |
| final uniquePlants = <String>{}; // Track objectNames we've seen | |
| final items = <DropdownMenuItem<String>>[]; | |
| // Create dropdown items from water treatment plants, handling duplicate objectNames | |
| for (final plant in controller.plantItems) { | |
| final objectName = plant.objectName.trim(); | |
| if (objectName.isEmpty || uniquePlants.contains(objectName)) { | |
| continue; | |
| } | |
| uniquePlants.add(objectName); | |
| items.add( | |
| DropdownMenuItem<String>( | |
| value: objectName, | |
| child: Text(objectName), | |
| ), | |
| ); | |
| } | |
| final currentValue = controller.selectedPlant?.objectName.trim(); | |
| final dropdownValue = items.any((entry) => entry.value == currentValue) | |
| ? currentValue | |
| : null; | |
| return CustomDropdownField<String>( | |
| label: "เลือกระบบผลิต", | |
| value: dropdownValue, | |
| onChanged: controller.plantItems.isNotEmpty | |
| ? (value) { | |
| // Find the plant that matches the selected objectName | |
| final selectedPlant = controller.plantItems.firstWhere( | |
| (plant) => plant.objectName.trim() == value?.trim(), | |
| orElse: () => controller.plantItems.first, | |
| ); | |
| // Select the plant directly by objectName | |
| controller.selectPlant(selectedPlant.objectName); | |
| } | |
| : null, | |
| enabled: controller.plantItems.isNotEmpty, | |
| hintText: controller.plantItems.isNotEmpty | |
| ? null | |
| : 'ไม่พบข้อมูลระบบผลิต', | |
| items: items, | |
| ); | |
| }, | |
| ), | |
| ]; | |
| List<Widget> _buildStep11(P607ViewModel controller, P600State args) => [ | |
| SectionHeader(title: "การเก็บตัวอย่างน้ำก่อนกรอง"), | |
| Builder( | |
| builder: (context) => PhotoUploadWidget( | |
| title: 'การเก็บตัวอย่างน้ำก่อนกรอง', | |
| photos: controller.currentEquipmentPhotos, | |
| maxPhotos: 5, | |
| enabled: controller.selectedPlant != null, | |
| onPhotoTaken: (photo) async { | |
| await controller.executeWithErrorHandling( | |
| context, | |
| () async => controller.addEquipmentPhoto(photo), | |
| operationContext: 'P607.addEquipmentPhoto', | |
| ); | |
| }, | |
| onRemovePhoto: (index) async { | |
| await controller.executeWithErrorHandling( | |
| context, | |
| () async => controller.removeEquipmentPhoto(index), | |
| operationContext: 'P607.removeEquipmentPhoto', | |
| ); | |
| }, | |
| ), | |
| ), | |
| ]; | |
| List<Widget> _buildStep12(P607ViewModel controller, P600State args) => [ | |
| SectionHeader(title: "ตรวจสอบประตูน้ำและอุปกรณ์ภายในโรงกรอง"), | |
| CustomSelectorField<bool>( | |
| label: 'สถานะ', | |
| enabled: controller.selectedPlant != null, | |
| initialValue: controller.inspectStatus, | |
| onSelectionChanged: controller.updateInspectStatus, | |
| options: [ | |
| RadioOption(value: true, label: "ปกติ"), | |
| RadioOption(value: false, label: "ไม่ปกติ"), | |
| ], | |
| ), | |
| if (controller.inspectStatus == false) ...[ | |
| CustomFormField( | |
| label: 'หมายเหตุ (โปรดระบุ)', | |
| enabled: controller.selectedPlant != null, | |
| controller: controller.inspectReasonController, | |
| maxLines: 3, | |
| ), | |
| ], | |
| ]; | |
| List<Widget> _buildStep2( | |
| BuildContext context, | |
| P607ViewModel controller, | |
| P600State args, | |
| ) { | |
| final filters = controller.filterGroupItems; | |
| return [ | |
| SectionHeader(title: _activityTitle(args, 1)), | |
| if (filters.isEmpty) | |
| const Text('ไม่พบข้อมูลหม้อกรอง') | |
| else | |
| Wrap( | |
| runSpacing: 16, | |
| children: filters.asMap().entries.map((entry) { | |
| final filter = entry.value; | |
| return SizedBox( | |
| width: (MediaQuery.of(context).size.width - 32) / 4, | |
| child: TextButton( | |
| style: TextButton.styleFrom( | |
| shape: RoundedRectangleBorder( | |
| borderRadius: BorderRadius.circular(16), | |
| ), | |
| ), | |
| onPressed: () async { | |
| // Select the filter first | |
| controller.selectFilter(filter.filterName); | |
| // Get draft filter data | |
| final draftFilter = controller.getDraftFilter(filter); | |
| // Show the modal and wait for result | |
| if (context.mounted) { | |
| final updatedFilter = await P607FilterUpdateModal.show( | |
| context, | |
| draftFilter, | |
| controller.filterId, | |
| args.item.operationDateTimestamp.split('T')[0], | |
| args.item.workScheduleId, | |
| filter.seq ?? 1, // Pass seq from filter, default to 1 | |
| ); | |
| // Update filter if user submitted changes | |
| if (updatedFilter != null && context.mounted) { | |
| await controller.executeWithErrorHandling( | |
| context, | |
| () async => controller.updateFilter(updatedFilter), | |
| operationContext: 'P607.updateFilter', | |
| ); | |
| } | |
| } | |
| }, | |
| child: Column( | |
| children: [ | |
| Image.asset( | |
| 'images/filter_machine.png', | |
| width: 64, | |
| height: 64, | |
| fit: BoxFit.contain, | |
| alignment: Alignment.center, | |
| ), | |
| SizedBox(height: 8), | |
| Text( | |
| controller.displayFilterLabel(filter.filterName), | |
| style: TextStyle( | |
| fontSize: 14, | |
| fontWeight: FontWeight.w500, | |
| height: 1.4, | |
| color: Color(0xFF191C20), | |
| ), | |
| textAlign: TextAlign.center, | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| }).toList(), | |
| ), | |
| ]; | |
| } | |
| List<Widget> _buildStep3(P607ViewModel controller, P600State args) { | |
| final filters = controller.filterGroupItems; | |
| return [ | |
| SectionHeader(title: _activityTitle(args, 2)), | |
| if (filters.isEmpty) | |
| const Text('ไม่พบข้อมูลหม้อกรอง') | |
| else | |
| DecoratedBox( | |
| decoration: BoxDecoration( | |
| borderRadius: BorderRadius.circular(12), | |
| border: Border.all(color: Colors.grey.shade300, width: 1), | |
| ), | |
| child: Column( | |
| children: [ | |
| Container( | |
| padding: const EdgeInsets.symmetric( | |
| vertical: 12, | |
| horizontal: 16, | |
| ), | |
| decoration: const BoxDecoration( | |
| color: Color(0xFF035BA9), | |
| borderRadius: BorderRadius.vertical(top: Radius.circular(12)), | |
| ), | |
| child: Row( | |
| children: const [ | |
| _HeaderCell(label: 'กะทำงาน', flex: 1), | |
| _HeaderCell(label: 'หม้อกรอง', flex: 2), | |
| _HeaderCell(label: 'สถานะ', flex: 3), | |
| ], | |
| ), | |
| ), | |
| ...filters.asMap().entries.map((entry) { | |
| final index = entry.key; | |
| final filter = entry.value; | |
| final statusCode = controller.filterStatusFor( | |
| filter.filterName, | |
| ); | |
| final statusLabel = controller.filterStatusLabel(statusCode); | |
| final statusRemark = controller.filterStatusRemark( | |
| filter.filterName, | |
| ); | |
| final remarkText = | |
| statusRemark != null && statusRemark.isNotEmpty | |
| ? '$statusLabel ($statusRemark)' | |
| : statusLabel; | |
| return Container( | |
| decoration: BoxDecoration( | |
| color: index.isEven ? Colors.white : Colors.grey[50], | |
| border: Border( | |
| bottom: BorderSide(color: Colors.grey.shade200, width: 1), | |
| ), | |
| ), | |
| padding: const EdgeInsets.symmetric( | |
| vertical: 12, | |
| horizontal: 16, | |
| ), | |
| child: Row( | |
| crossAxisAlignment: CrossAxisAlignment.center, | |
| children: [ | |
| Expanded( | |
| flex: 1, | |
| child: Text( | |
| controller.currentShiftLabel, | |
| style: const TextStyle(fontSize: 14), | |
| textAlign: TextAlign.center, | |
| ), | |
| ), | |
| Expanded( | |
| flex: 2, | |
| child: Text( | |
| controller.displayFilterNumber(filter.filterName), | |
| style: const TextStyle(fontSize: 14), | |
| textAlign: TextAlign.center, | |
| ), | |
| ), | |
| Expanded( | |
| flex: 3, | |
| child: Row( | |
| children: [ | |
| Expanded( | |
| child: Text( | |
| remarkText, | |
| style: const TextStyle( | |
| fontSize: 14, | |
| color: Color(0xFF191C20), | |
| ), | |
| ), | |
| ), | |
| _StatusDot(statusCode: statusCode), | |
| ], | |
| ), | |
| ), | |
| ], | |
| ), | |
| ); | |
| }), | |
| ], | |
| ), | |
| ), | |
| ]; | |
| } | |
| List<Widget> _buildStep4(P607ViewModel controller, P600State args) => [ | |
| SectionHeader(title: _activityTitle(args, 3)), | |
| Builder( | |
| builder: (context) => PhotoUploadWidget( | |
| title: 'การเก็บตัวอย่างน้ำหลังกรอง', | |
| photos: controller.currentAfterFilteringPhotos, | |
| maxPhotos: 5, | |
| enabled: controller.selectedPlant != null, | |
| onPhotoTaken: (photo) async { | |
| await controller.executeWithErrorHandling( | |
| context, | |
| () async => controller.addAfterFilteringPhoto(photo), | |
| operationContext: 'P607.addAfterFilteringPhoto', | |
| ); | |
| }, | |
| onRemovePhoto: (index) async { | |
| await controller.executeWithErrorHandling( | |
| context, | |
| () async => controller.removeAfterFilteringPhoto(index), | |
| operationContext: 'P607.removeAfterFilteringPhoto', | |
| ); | |
| }, | |
| ), | |
| ), | |
| ]; | |
| String _activityTitle(P600State args, int index) { | |
| if (index >= 0 && index < args.item.submanu.length) { | |
| return args.item.submanu[index].activityName; | |
| } | |
| return 'ไม่ระบุหัวข้อ'; | |
| } | |
| } | |
| class _HeaderCell extends StatelessWidget { | |
| final String label; | |
| final int flex; | |
| const _HeaderCell({required this.label, this.flex = 1}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return Expanded( | |
| flex: flex, | |
| child: Text( | |
| label, | |
| textAlign: TextAlign.center, | |
| style: const TextStyle( | |
| color: Colors.white, | |
| fontSize: 14, | |
| fontWeight: FontWeight.w600, | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class _StatusDot extends StatelessWidget { | |
| final String? statusCode; | |
| final StatusMappingService _statusMapping = StatusMappingService(); | |
| _StatusDot({required this.statusCode}); | |
| @override | |
| Widget build(BuildContext context) { | |
| return Container( | |
| width: 14, | |
| height: 14, | |
| margin: const EdgeInsets.only(left: 8), | |
| decoration: BoxDecoration( | |
| color: _statusMapping.getStatusColor(statusCode), | |
| shape: BoxShape.circle, | |
| ), | |
| ); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| import 'package:provider/provider.dart'; | |
| import 'package:pwa_ocs_flutter/ui/core/ui/high_pressure_water_meter_form_dialog.dart'; | |
| import 'package:pwa_ocs_flutter/ui/core/ui/high_pressure_water_pump_form_dialog.dart'; | |
| import 'package:pwa_ocs_flutter/features/p609/domain/entities/high_pressure_water.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/custom_form_field.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/loading_view.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/section_header.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/view_only_step_navigator.dart'; | |
| import 'package:pwa_ocs_flutter/ui/core/ui/main_scaffold.dart'; | |
| import 'package:pwa_ocs_flutter/features/p600/presentation/bloc/p600_viewmodel.dart'; | |
| import 'package:pwa_ocs_flutter/features/p609/presentation/bloc/p609_viewmodel.dart'; | |
| class P609 extends StatelessWidget { | |
| P609({super.key}); | |
| @override | |
| Widget build(context) { | |
| final args = ModalRoute.of(context)!.settings.arguments as P600State; | |
| return ChangeNotifierProvider<P609ViewModel>( | |
| create: (context) => P609ViewModel( | |
| args.item.plantId, | |
| args.item.operationDateTimestamp.split('T')[0], | |
| args.item.workScheduleId, | |
| initialStep: args.item.submanu[args.index].activitySeq, | |
| ), | |
| child: Consumer<P609ViewModel>( | |
| builder: (context, controller, child) { | |
| if (controller.isLoading) { | |
| return MainScaffold( | |
| title: args.item.plantName, | |
| subtitle: args.item.operationDate, | |
| child: const LoadingView(), | |
| ); | |
| } else if (controller.error != null) { | |
| return MainScaffold( | |
| title: args.item.plantName, | |
| subtitle: args.item.operationDate, | |
| child: Center( | |
| child: Padding( | |
| padding: const EdgeInsets.all(16), | |
| child: Text('Error: ${controller.error}'), | |
| ), | |
| ), | |
| ); | |
| } | |
| final items = [ | |
| _buildBase(controller), | |
| if (controller.objectId != null) ...[ | |
| if (controller.currentStep == 1) | |
| ..._buildStep1(context, controller, args), | |
| if (controller.currentStep == 2) | |
| ..._buildStep2(context, controller, args), | |
| ], | |
| ]; | |
| return MainScaffold( | |
| title: args.item.plantName, | |
| subtitle: args.item.operationDate, | |
| child: Form( | |
| key: controller.key, | |
| child: Container( | |
| padding: const EdgeInsets.symmetric(vertical: 16.0), | |
| child: Column( | |
| spacing: 16.0, | |
| children: [ | |
| Expanded( | |
| child: RefreshIndicator( | |
| onRefresh: controller.fetchAndUpdateCache, | |
| child: ListView.separated( | |
| padding: const EdgeInsets.symmetric( | |
| vertical: 24, | |
| horizontal: 16, | |
| ), | |
| itemCount: items.length, | |
| itemBuilder: (context, index) => items[index], | |
| separatorBuilder: (context, index) => | |
| const SizedBox(height: 24.0), | |
| ), | |
| ), | |
| ), | |
| ViewOnlyStepNavigator( | |
| onNext: controller.next, | |
| current: controller.currentStep, | |
| onPrevious: controller.prev, | |
| total: P609ViewModel.MAX_STEP, | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| }, | |
| ), | |
| ); | |
| } | |
| Widget _buildBase(P609ViewModel controller) => CustomDropdownField( | |
| label: "เลือกโรงสูบน้ำแรงสูง", | |
| key: const Key('object_id'), | |
| value: controller.objectId, | |
| onChanged: controller.selectHighPressureWater, | |
| items: controller.highPressureWaterList | |
| .map( | |
| (option) => DropdownMenuItem( | |
| child: Text(option.objectName ?? ''), | |
| value: option.objectId, | |
| ), | |
| ) | |
| .toList(), | |
| ); | |
| List<Widget> _buildStep1( | |
| BuildContext context, | |
| P609ViewModel controller, | |
| P600State args, | |
| ) { | |
| final selectedHighPressure = _findSelectedHighPressure(controller); | |
| return [ | |
| SectionHeader(title: _activityTitle(args, 0)), | |
| if (selectedHighPressure == null) | |
| const Text('ไม่พบข้อมูลโรงสูบที่เลือก') | |
| else | |
| Wrap( | |
| runSpacing: 16, | |
| children: selectedHighPressure.highPressurePumpList | |
| .asMap() | |
| .entries | |
| .map((entry) { | |
| final index = entry.key; | |
| final pump = entry.value; | |
| return SizedBox( | |
| width: (MediaQuery.of(context).size.width - 32) / 4, | |
| child: TextButton( | |
| style: TextButton.styleFrom( | |
| shape: RoundedRectangleBorder( | |
| borderRadius: BorderRadius.circular(16), | |
| ), | |
| ), | |
| onPressed: () { | |
| final initialPressureWaterPump = controller | |
| .getDraftHighPressureWaterPump(pump); | |
| showDialog( | |
| context: context, | |
| builder: (dialogContext) { | |
| final formKey = GlobalKey<FormState>(); | |
| return HighPressureWaterPumpFormDialog( | |
| formKey: formKey, | |
| pressureWaterPump: initialPressureWaterPump, | |
| plantId: controller.plantId, | |
| operationDate: controller.operationDate, | |
| workScheduleId: controller.workScheduleId, | |
| onSubmit: (highPressurePump) => | |
| controller.updateHighPressureWaterPump( | |
| index, | |
| highPressurePump, | |
| ), | |
| ); | |
| }, | |
| ); | |
| }, | |
| child: Column( | |
| children: [ | |
| Image.asset( | |
| _getPumpImagePath(pump), | |
| width: 64, | |
| height: 64, | |
| fit: BoxFit.contain, | |
| alignment: Alignment.center, | |
| ), | |
| const SizedBox(height: 8), | |
| Text( | |
| pump.pumpName ?? '', | |
| style: const TextStyle( | |
| fontSize: 14, | |
| fontWeight: FontWeight.w500, | |
| height: 1.4, | |
| color: Color(0xFF191C20), | |
| ), | |
| textAlign: TextAlign.center, | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| }) | |
| .toList(), | |
| ), | |
| ]; | |
| } | |
| List<Widget> _buildStep2( | |
| BuildContext context, | |
| P609ViewModel controller, | |
| P600State args, | |
| ) { | |
| final selectedHighPressure = _findSelectedHighPressure(controller); | |
| return [ | |
| SectionHeader(title: _activityTitle(args, 1)), | |
| if (selectedHighPressure == null) | |
| const Text('ไม่พบข้อมูลโรงสูบที่เลือก') | |
| else | |
| Wrap( | |
| runSpacing: 16, | |
| children: selectedHighPressure.highPressureMeterList.asMap().entries.map(( | |
| entry, | |
| ) { | |
| final index = entry.key; | |
| final meter = entry.value; | |
| return SizedBox( | |
| width: (MediaQuery.of(context).size.width - 32) / 4, | |
| child: TextButton( | |
| style: TextButton.styleFrom( | |
| shape: RoundedRectangleBorder( | |
| borderRadius: BorderRadius.circular(16), | |
| ), | |
| ), | |
| onPressed: () { | |
| final initialPressureWaterMeter = controller | |
| .getDraftHighPressureWaterMeter(meter); | |
| showDialog( | |
| context: context, | |
| builder: (dialogContext) { | |
| final formKey = GlobalKey<FormState>(); | |
| return HighPressureWaterMeterFormDialog( | |
| formKey: formKey, | |
| pressureWaterMeter: initialPressureWaterMeter, | |
| plantId: controller.plantId, | |
| operationDate: controller.operationDate, | |
| workScheduleId: controller.workScheduleId, | |
| onSubmit: (highPressureMeter) => | |
| controller.updateHighPressureWaterMeter( | |
| index, | |
| highPressureMeter, | |
| ), | |
| ); | |
| }, | |
| ); | |
| }, | |
| child: Column( | |
| children: [ | |
| Image.asset( | |
| 'images/raw-water-meter${meter.distributionPumpStatus != null && meter.distributionPumpStatus!.isNotEmpty ? '-${meter.distributionPumpStatus!.toLowerCase()}' : ""}.png', | |
| width: 64, | |
| height: 64, | |
| fit: BoxFit.contain, | |
| alignment: Alignment.center, | |
| ), | |
| const SizedBox(height: 8), | |
| Text( | |
| meter.meterName ?? '', | |
| style: const TextStyle( | |
| fontSize: 14, | |
| fontWeight: FontWeight.w500, | |
| height: 1.4, | |
| color: Color(0xFF191C20), | |
| ), | |
| textAlign: TextAlign.center, | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| }).toList(), | |
| ), | |
| ]; | |
| } | |
| String _activityTitle(P600State args, int index) { | |
| if (index >= 0 && index < args.item.submanu.length) { | |
| final name = args.item.submanu[index].activityName; | |
| return name.isNotEmpty ? name : 'ไม่ระบุหัวข้อ'; | |
| } | |
| return 'ไม่ระบุหัวข้อ'; | |
| } | |
| HighPressureWater? _findSelectedHighPressure(P609ViewModel controller) { | |
| for (final item in controller.highPressureWaterList) { | |
| if (item.objectId == controller.objectId) { | |
| return item; | |
| } | |
| } | |
| return null; | |
| } | |
| /// Get the correct image asset path for water pump based on status | |
| /// Logic (Priority order): | |
| /// 1. machineRemark = "1" → raw-water-pump-1.png (พักเครื่อง) | |
| /// 2. machineRemark = "2" → raw-water-pump-2.png (สำรอง) | |
| /// 3. machineRemark = "3" → raw-water-pump-3.png (ใช้งานไม่ได้) | |
| /// 4. machineRemark = "4" → raw-water-pump-4.png (แจ้งซ่อม) | |
| /// 5. pumpStatus = "A" → Red (ใช้งาน) | |
| /// 6. pumpStatus = "I" with no machineRemark → Blue (ไม่ใช้งาน) | |
| /// 7. Default → Blue (ไม่มีข้อมูล) | |
| String _getPumpImagePath(dynamic pump) { | |
| // DEBUG: Log current pump status and machine remark | |
| debugPrint( | |
| '🔍 P609: Pump "${pump.pumpName ?? 'Unknown'}" - pumpStatus: "${pump.pumpStatus ?? ''}", machineRemark: "${pump.operationStatusRemark ?? ''}"', | |
| ); | |
| // Priority 1: Check operation machineRemark field (highest priority) | |
| final machineRemark = pump.operationStatusRemark; | |
| if (machineRemark != null && machineRemark.isNotEmpty) { | |
| debugPrint(' 🎯 Machine remark found: $machineRemark'); | |
| if (machineRemark == '1') { | |
| return 'images/raw-water-pump-1.png'; // พักเครื่อง - สีเขียว | |
| } else if (machineRemark == '2') { | |
| return 'images/raw-water-pump-2.png'; // สำรอง - สีเขียว | |
| } else if (machineRemark == '3') { | |
| return 'images/raw-water-pump-3.png'; // ใช้งานไม่ได้ - สีเทา | |
| } else if (machineRemark == '4') { | |
| return 'images/raw-water-pump-4.png'; // แจ้งซ่อม - สีเทา | |
| } | |
| } | |
| // Priority 2: Check pumpStatus = "A" → Red (Active/Running) | |
| final pumpStatus = pump.pumpStatus; | |
| if (pumpStatus != null && pumpStatus == 'A') { | |
| debugPrint(' 🎯 Pump status is A (Active)'); | |
| return 'images/raw-water-pump-a.png'; // ใช้งาน - สีแดง | |
| } | |
| // Priority 3: Check pumpStatus = "I" with no machineRemark → Blue (Inactive) | |
| if (pumpStatus != null && | |
| pumpStatus == 'I' && | |
| (machineRemark == null || machineRemark.isEmpty)) { | |
| debugPrint(' 🎯 Pump status is I (Inactive) with no machine remark'); | |
| return 'images/raw-water-pump-i.png'; // ไม่ใช้งาน - สีน้ำเงิน | |
| } | |
| // Default: Blue - ไม่มีข้อมูล | |
| debugPrint(' 🎯 Default case - using blue image'); | |
| return 'images/raw-water-pump-i.png'; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| import 'package:provider/provider.dart'; | |
| import 'package:pwa_ocs_flutter/ui/core/ui/main_scaffold.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/loading_view.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/photo_widget.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/section_header.dart'; | |
| import 'package:pwa_ocs_flutter/features/p600/presentation/bloc/p600_viewmodel.dart'; | |
| import 'package:pwa_ocs_flutter/features/p610/presentation/bloc/p610_viewmodel.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/step_navigation.dart'; | |
| import 'package:pwa_ocs_flutter/shared/widgets/custom_form_field.dart'; | |
| import 'package:pwa_ocs_flutter/ui/core/ui/reverse_osmosis_pump_form_dialog.dart'; | |
| import 'package:pwa_ocs_flutter/features/p610/data/datasources/p610_service.dart'; | |
| class P610 extends StatefulWidget { | |
| const P610({super.key}); | |
| @override | |
| State<P610> createState() => _P610State(); | |
| } | |
| class _P610State extends State<P610> { | |
| // Cache text controllers to avoid recreating them on each rebuild | |
| final Map<String, TextEditingController> _textControllers = {}; | |
| @override | |
| void dispose() { | |
| // Dispose all text controllers | |
| for (final controller in _textControllers.values) { | |
| controller.dispose(); | |
| } | |
| super.dispose(); | |
| } | |
| Future<void> _handleSubmit( | |
| BuildContext context, | |
| P610ViewModel controller, | |
| P600State args, | |
| ) async { | |
| final result = await controller.onSubmit( | |
| args.item.plantId, | |
| args.item.operationDateTimestamp.split('T')[0], | |
| ); | |
| if (!context.mounted) return; | |
| if (result == 'invalid') { | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| SnackBar( | |
| content: Row( | |
| children: const [ | |
| Icon(Icons.warning_amber_rounded, color: Colors.white), | |
| SizedBox(width: 12), | |
| Expanded(child: Text('กรุณากรอกข้อมูลให้ครบทุกขั้นตอน')), | |
| ], | |
| ), | |
| backgroundColor: Colors.orange[600], | |
| behavior: SnackBarBehavior.floating, | |
| margin: const EdgeInsets.all(16), | |
| shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), | |
| duration: const Duration(seconds: 4), | |
| action: SnackBarAction( | |
| label: 'ปิด', | |
| textColor: Colors.white, | |
| onPressed: () { | |
| ScaffoldMessenger.of(context).hideCurrentSnackBar(); | |
| }, | |
| ), | |
| ), | |
| ); | |
| return; | |
| } | |
| if (result == 'success') { | |
| showDialog( | |
| context: context, | |
| barrierDismissible: false, | |
| builder: (dialogContext) => AlertDialog( | |
| title: const Text('ส่งข้อมูลสำเร็จ', textAlign: TextAlign.center), | |
| content: Column( | |
| mainAxisSize: MainAxisSize.min, | |
| spacing: 16, | |
| children: const [ | |
| Icon(Icons.check_circle, size: 64, color: Colors.green), | |
| Text('กำลังกลับไปหน้ารายการ...'), | |
| ], | |
| ), | |
| ), | |
| ); | |
| await Future.delayed(const Duration(seconds: 2)); | |
| if (!context.mounted) return; | |
| Navigator.of(context).pushReplacementNamed('/600', arguments: args); | |
| return; | |
| } | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| SnackBar( | |
| content: Row( | |
| spacing: 12, | |
| children: const [ | |
| Icon(Icons.error_outline, color: Colors.white), | |
| Expanded(child: Text('ส่งข้อมูลไม่สำเร็จ')), | |
| ], | |
| ), | |
| backgroundColor: Colors.red[600], | |
| behavior: SnackBarBehavior.floating, | |
| margin: const EdgeInsets.all(16), | |
| shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), | |
| action: SnackBarAction( | |
| label: 'ปิด', | |
| textColor: Colors.white, | |
| onPressed: () { | |
| ScaffoldMessenger.of(context).hideCurrentSnackBar(); | |
| }, | |
| ), | |
| ), | |
| ); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| final args = ModalRoute.of(context)!.settings.arguments as P600State; | |
| return ChangeNotifierProvider<P610ViewModel>( | |
| create: (context) => P610ViewModel( | |
| args.item.plantId, | |
| args.item.operationDateTimestamp, | |
| args.item.workScheduleId, | |
| args.item.submanu[args.index].activitySeq, | |
| ), | |
| child: Consumer<P610ViewModel>( | |
| builder: (context, controller, child) { | |
| if (controller.isLoading) { | |
| return MainScaffold( | |
| title: args.item.plantName, | |
| subtitle: args.item.operationDateTimestamp.split('T')[0], | |
| child: const LoadingView(), | |
| ); | |
| } | |
| if (controller.errorMessage != null) { | |
| return MainScaffold( | |
| title: args.item.plantName, | |
| subtitle: args.item.operationDateTimestamp.split('T')[0], | |
| child: Center(child: Text('Error: ${controller.errorMessage}')), | |
| ); | |
| } | |
| final items = [ | |
| if (controller.step == 1) ..._buildStep1(controller), | |
| if (controller.step == 2) ..._buildStep2(context, controller, args), | |
| ]; | |
| return MainScaffold( | |
| title: args.item.plantName, | |
| subtitle: args.item.operationDate, | |
| child: Form( | |
| key: controller.key, | |
| child: Container( | |
| padding: const EdgeInsets.symmetric(vertical: 16.0), | |
| child: Column( | |
| children: [ | |
| Expanded( | |
| child: RefreshIndicator( | |
| onRefresh: controller.fetchAndUpdateCache, | |
| child: ListView.separated( | |
| padding: const EdgeInsets.symmetric( | |
| vertical: 12, | |
| horizontal: 16, | |
| ), | |
| itemCount: items.length, | |
| itemBuilder: (context, index) => items[index], | |
| separatorBuilder: (context, index) => | |
| const SizedBox(height: 24.0), | |
| ), | |
| ), | |
| ), | |
| StepNavigator( | |
| total: P610ViewModel.MAX_STEP, | |
| submitting: false, | |
| submitable: controller.isFormValid(), | |
| onNext: controller.next, | |
| current: controller.step, | |
| onPrevious: controller.prev, | |
| onSubmit: () => _handleSubmit(context, controller, args), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| }, | |
| ), | |
| ); | |
| } | |
| List<Widget> _buildStep1(P610ViewModel controller) => [ | |
| SectionHeader(title: 'สระพักตะกอน'), | |
| CustomDropdownField<String>( | |
| label: 'เลือกสระพักตะกอน', | |
| value: controller.selectedTankId, | |
| onChanged: controller.sedimentationTanks.isNotEmpty | |
| ? controller.selectTank | |
| : null, | |
| enabled: controller.sedimentationTanks.isNotEmpty, | |
| hintText: controller.sedimentationTanks.isEmpty | |
| ? 'ไม่พบข้อมูลสระพักตะกอน' | |
| : null, | |
| items: controller.tankDropdownOptions, | |
| ), | |
| if (controller.selectedTankId != null) ...[ | |
| Builder( | |
| builder: (context) { | |
| return PhotoUploadWidget( | |
| title: 'ถ่ายรูปสระพักตะกอน', | |
| photos: controller.photoList1, | |
| maxPhotos: 5, | |
| enabled: controller.selectedTankId != null, | |
| onPhotoTaken: controller.addTankPhoto, | |
| onRemovePhoto: controller.removeTankPhoto, | |
| ); | |
| }, | |
| ), | |
| ], | |
| ]; | |
| List<Widget> _buildStep2( | |
| BuildContext context, | |
| P610ViewModel controller, | |
| P600State args, | |
| ) { | |
| final selectedStation = controller.selectedStation; | |
| return [ | |
| SectionHeader(title: 'โรงสูบน้ำย้อนกลับ'), | |
| CustomDropdownField<String>( | |
| label: 'เลือกโรงสูบน้ำย้อนกลับ', | |
| value: controller.selectedStationId, | |
| onChanged: controller.reverseOsmosisStations.isNotEmpty | |
| ? controller.selectStation | |
| : null, | |
| enabled: controller.reverseOsmosisStations.isNotEmpty, | |
| hintText: controller.reverseOsmosisStations.isEmpty | |
| ? 'ไม่พบข้อมูลโรงสูบน้ำย้อนกลับ' | |
| : null, | |
| items: controller.stationDropdownOptions, | |
| ), | |
| if (selectedStation == null) | |
| const Text('ไม่พบข้อมูลสถานีที่เลือก') | |
| else | |
| Wrap( | |
| runSpacing: 16, | |
| children: controller.pumpsForSelectedStation.asMap().entries.map(( | |
| entry, | |
| ) { | |
| final index = entry.key; | |
| final pump = entry.value; | |
| return SizedBox( | |
| width: (MediaQuery.of(context).size.width - 32) / 4, | |
| child: TextButton( | |
| style: TextButton.styleFrom( | |
| shape: RoundedRectangleBorder( | |
| borderRadius: BorderRadius.circular(16), | |
| ), | |
| ), | |
| onPressed: () { | |
| final initialPump = controller.getDraftReverseOsmosisPump( | |
| pump, | |
| ); | |
| showDialog( | |
| context: context, | |
| builder: (context) { | |
| final formKey = GlobalKey<FormState>(); | |
| return ReverseOsmosisPumpFormDialog( | |
| formKey: formKey, | |
| pump: initialPump, | |
| operationDate: args.item.operationDateTimestamp.split( | |
| 'T', | |
| )[0], | |
| workScheduleId: args.item.workScheduleId, | |
| onSubmit: (updatedPump) => controller | |
| .updateReverseOsmosisPump(index, updatedPump), | |
| ); | |
| }, | |
| ); | |
| }, | |
| child: Column( | |
| children: [ | |
| Image.asset( | |
| _getPumpImagePath(pump), | |
| width: 64, | |
| height: 64, | |
| fit: BoxFit.contain, | |
| alignment: Alignment.center, | |
| ), | |
| const SizedBox(height: 8), | |
| Text( | |
| pump.reverseOsmosisPumpName, | |
| style: const TextStyle( | |
| fontSize: 14, | |
| fontWeight: FontWeight.w500, | |
| height: 1.4, | |
| color: Color(0xFF191C20), | |
| ), | |
| textAlign: TextAlign.center, | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| }).toList(), | |
| ), | |
| ]; | |
| } | |
| /// Get the correct image asset path for water pump based on status | |
| /// Logic (Priority order): | |
| /// 1. machineRemark = "1" → raw-water-pump-1.png (พักเครื่อง) | |
| /// 2. machineRemark = "2" → raw-water-pump-2.png (สำรอง) | |
| /// 3. machineRemark = "3" → raw-water-pump-3.png (ใช้งานไม่ได้) | |
| /// 4. machineRemark = "4" → raw-water-pump-4.png (แจ้งซ่อม) | |
| /// 5. pumpStatus = "A" → Red (ใช้งาน) | |
| /// 6. pumpStatus = "I" with no machineRemark → Blue (ไม่ใช้งาน) | |
| /// 7. Default → Blue (ไม่มีข้อมูล) | |
| String _getPumpImagePath(ReverseOsmosisPump pump) { | |
| // DEBUG: Log the current pump status and machine remark | |
| debugPrint( | |
| '🔍 P610: Pump "${pump.reverseOsmosisPumpName}" - pumpStatus: "${pump.pumpStatus}", machineRemark: "${pump.machineRemark}"', | |
| ); | |
| // Priority 1: Check machineRemark for specific image (highest priority) | |
| if (pump.machineRemark.isNotEmpty) { | |
| final remark = pump.machineRemark; | |
| debugPrint(' 🎯 Machine remark found: $remark'); | |
| if (remark == '1') { | |
| return 'images/raw-water-pump-1.png'; // พักเครื่อง - สีเขียว | |
| } else if (remark == '2') { | |
| return 'images/raw-water-pump-2.png'; // สำรอง - สีเขียว | |
| } else if (remark == '3') { | |
| return 'images/raw-water-pump-3.png'; // ใช้งานไม่ได้ - สีเทา | |
| } else if (remark == '4') { | |
| return 'images/raw-water-pump-4.png'; // แจ้งซ่อม - สีเทา | |
| } | |
| } | |
| // Priority 2: Check pumpStatus = "A" → Red (Active/Running) | |
| if (pump.pumpStatus == 'A') { | |
| debugPrint(' 🎯 Pump status is A (Active)'); | |
| return 'images/raw-water-pump-a.png'; // ใช้งาน - สีแดง | |
| } | |
| // Priority 3: Check pumpStatus = "I" with no machineRemark → Blue (Inactive) | |
| if (pump.pumpStatus == 'I' && pump.machineRemark.isEmpty) { | |
| debugPrint(' 🎯 Pump status is I (Inactive) with no machine remark'); | |
| return 'images/raw-water-pump-i.png'; // ไม่ใช้งาน - สีน้ำเงิน | |
| } | |
| // Default: Blue - ไม่มีข้อมูล | |
| debugPrint(' 🎯 Default case - using blue image'); | |
| return 'images/raw-water-pump-i.png'; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment