import 'dart:convert'; import 'dart:ui'; import 'package:go_router/go_router.dart'; import 'package:proto_portal/scraper.dart'; import 'package:proto_portal/widgets/service_card.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; class AuditPage extends StatefulWidget { static GoRoute route = GoRoute( path: "/audit", builder: (context, state) { final serviceBase64 = state.uri.queryParameters['service'] ?? ''; final stationBase64 = state.uri.queryParameters['station'] ?? ''; GtiService? service; String? stationLabel; String? crs; try { // Decode service from base64 final serviceJson = utf8.decode(base64Decode(serviceBase64)); service = GtiService.fromJson(jsonDecode(serviceJson)); // Decode station from base64 final stationJson = utf8.decode(base64Decode(stationBase64)); final stationData = jsonDecode(stationJson); stationLabel = stationData['label']; crs = stationData['crs']; } catch (e) { print('Error decoding params: $e'); } return AuditPage( service: service, stationLabel: stationLabel ?? '', crs: crs ?? '', ); } ); final GtiService? service; final String stationLabel; final String crs; AuditPage({ required this.service, required this.stationLabel, required this.crs, }); @override State createState() => _AuditPageState(); } class _AuditPageState extends State { // Form controllers final TextEditingController _vehicleRegController = TextEditingController(); // Form values int _seats1 = 5; int _seats2 = 1; int _pax1 = 0; int _pax2 = 0; String _luggage = '0'; String _paceNotes = '1'; CheckboxState _vehicleTracking = CheckboxState.unchecked; CheckboxState _destBanner = CheckboxState.checked; CheckboxState _callingPatternFront = CheckboxState.unchecked; CheckboxState _callingPatternMiddle = CheckboxState.unchecked; CheckboxState _callingPatternRear = CheckboxState.unchecked; CheckboxState _audioEquipment = CheckboxState.unchecked; CheckboxState _audioWorking = CheckboxState.unchecked; CheckboxState _wheelchair = CheckboxState.checked; String _exemption = 'X'; @override void dispose() { _vehicleRegController.dispose(); super.dispose(); } Future _saveAudit() async { final auth = GtiAuth.of(context); print('=== AUDIT FORM DEBUG ==='); print('Service: ${widget.service}'); print('Service vehicleId: ${widget.service?.vehicleId}'); print('Service auditId: ${widget.service?.auditId}'); print('Station CRS: ${widget.crs}'); print('======================'); if (widget.service == null) { showDialog( context: context, builder: (context) => AlertDialog( title: Text('Error'), content: Text('Service data not loaded'), actions: [ PrimaryButton( onPressed: () => Navigator.of(context).pop(), child: Text('OK'), ), ], ), ); return; } try { final success = await auth.saveAudit( time: widget.service!.time, crs: widget.crs, vehicleId: widget.service!.vehicleId, auditId: widget.service!.auditId ?? '0', vehicleReg: _vehicleRegController.text, seats: '$_seats1$_seats2', pax: '$_pax1$_pax2', luggage: _luggage, paceNotes: _paceNotes, vehicleTracking: _vehicleTracking == CheckboxState.checked ? 'Y' : 'N', destBanner: _destBanner == CheckboxState.checked ? 'Y' : 'N', callingPatternFront: _callingPatternFront == CheckboxState.checked ? 'Y' : 'N', callingPatternMiddle: _callingPatternMiddle == CheckboxState.checked ? 'Y' : 'N', callingPatternRear: _callingPatternRear == CheckboxState.checked ? 'Y' : 'N', audioEquipment: _audioEquipment == CheckboxState.checked ? 'Y' : 'N', audioWorking: _audioWorking == CheckboxState.checked ? 'Y' : 'N', wheelchair: _wheelchair == CheckboxState.checked ? 'Y' : 'N', exemption: _exemption, ); if (success) { showDialog( context: context, builder: (context) => AlertDialog( title: Text('Success'), content: Text('Audit saved successfully!\n\nCheck console for request details.'), actions: [ PrimaryButton( onPressed: () { Navigator.of(context).pop(); GoRouter.of(context).pop(); }, child: Text('OK'), ), ], ), ); } else { showDialog( context: context, builder: (context) => AlertDialog( title: Text('Error'), content: Text('Failed to save audit.\n\nCheck console for details.\n\nVehicleId: ${widget.service!.vehicleId}\nAuditId: ${widget.service!.auditId}'), actions: [ PrimaryButton( onPressed: () => Navigator.of(context).pop(), child: Text('OK'), ), ], ), ); } } catch (e) { showDialog( context: context, builder: (context) => AlertDialog( title: Text('Error'), content: Text('Error saving audit: $e'), actions: [ PrimaryButton( onPressed: () => Navigator.of(context).pop(), child: Text('OK'), ), ], ), ); } } @override Widget build(BuildContext context) { double topPadding = MediaQuery.of(context).padding.top; double bottomPadding = MediaQuery.of(context).padding.bottom; if (widget.service == null) { return _scaffoldBgAndTing( context: context, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(LucideIcons.triangleAlert, size: 48), SizedBox(height: 16), Text('Service not found').h4, SizedBox(height: 8), OutlineButton( onPressed: () => GoRouter.of(context).pop(), child: Text('Go Back'), ), ], ), ), ); } return _scaffoldBgAndTing( context: context, child: Column( children: [ SizedBox(height: topPadding), // Header Container( height: 60, alignment: Alignment.center, child: Row( children: [ SizedBox(width: 8), Button.ghost( onPressed: () => GoRouter.of(context).pop(), child: Icon(LucideIcons.arrowLeft), ), Expanded( child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Audit Form').h4, Text('${widget.service?.coach} - ${widget.service?.time}').small.muted, ], ), ), ), SizedBox(width: 48), ], ), ), SizedBox(height: 8), // Form Content Expanded( child: SingleChildScrollView( padding: EdgeInsets.all(16), child: OutlinedContainer( child: Column( children: [ ServiceCard.fromService( service: widget.service!, ), // Read-only fields _buildReadOnlyRow('Schedule Depart Time:', widget.service?.time ?? ''), Divider(), _buildReadOnlyRow('Diagram Number:', widget.service?.coach ?? ''), Divider(), _buildReadOnlyRow('End Destination on Route:', widget.service?.destination ?? 'N/A'), Divider(), _buildReadOnlyRow('Coach Operator:', widget.service?.operator ?? ''), Divider(thickness: 2), // Editable fields _buildTextFieldRow( label: 'Vehicle Reg:', controller: _vehicleRegController, maxLength: 8, ), Divider(), _buildTwoDropdownRow( label: 'Vehicle (No. Seats):', value1: _seats1, value2: _seats2, onChanged1: (val) => setState(() => _seats1 = val!), onChanged2: (val) => setState(() => _seats2 = val!), ), Divider(), _buildTwoDropdownRow( label: 'Pax No.:', value1: _pax1, value2: _pax2, onChanged1: (val) => setState(() => _pax1 = val!), onChanged2: (val) => setState(() => _pax2 = val!), ), Divider(), _buildDropdownRow( label: 'Luggage Hold:', value: _luggage, items: [ ('0', 'Select'), ('1', 'OK'), ('2', 'Nearly Full'), ('3', 'Full'), ], onChanged: (val) => setState(() => _luggage = val!), ), Divider(), _buildDropdownRow( label: 'Vehicle (Pace Notes):', value: _paceNotes, items: [ ('1', 'Yes - FTS Supplied'), ('2', 'Yes - Company Supplied'), ('3', 'Yes - Sat Nav'), ('4', 'No - Nothing'), ], onChanged: (val) => setState(() => _paceNotes = val!), ), Divider(), _buildCheckboxRow( label: 'Does the service have Vehicle Tracking:', state: _vehicleTracking, onChanged: (val) => setState(() => _vehicleTracking = val), ), Divider(), _buildCheckboxRow( label: 'Diagram Number & Destination Banner:', state: _destBanner, onChanged: (val) => setState(() => _destBanner = val), ), Divider(), _buildCheckboxRow( label: 'Destination & Calling Pattern (Front):', state: _callingPatternFront, onChanged: (val) => setState(() => _callingPatternFront = val), ), Divider(), _buildCheckboxRow( label: 'Destination & Calling Pattern (Middle):', state: _callingPatternMiddle, onChanged: (val) => setState(() => _callingPatternMiddle = val), ), Divider(), _buildCheckboxRow( label: 'Destination & Calling Pattern (Rear):', state: _callingPatternRear, onChanged: (val) => setState(() => _callingPatternRear = val), ), Divider(), _buildCheckboxRow( label: 'Audio Announcement Equipment:', state: _audioEquipment, onChanged: (val) => setState(() => _audioEquipment = val), ), Divider(), _buildCheckboxRow( label: 'Audio Announcement Working:', state: _audioWorking, onChanged: (val) => setState(() => _audioWorking = val), ), Divider(), Padding( padding: EdgeInsets.all(12), child: Text( 'Where Audio Announcement is \'No\', please instruct the driver to perform verbal announcements' ).small.muted, ), Divider(), _buildCheckboxRow( label: 'Wheelchair Accessible:', state: _wheelchair, onChanged: (val) => setState(() => _wheelchair = val), ), Divider(), _buildDropdownRow( label: 'Where Wheelchair Accessible is \'No\', is an MTE Exemption certificate visible?:', value: _exemption, items: [ ('X', 'N/A'), ('N', 'N'), ('Y', 'Y'), ], onChanged: (val) => setState(() => _exemption = val!), ), Divider(), // Save button Padding( padding: EdgeInsets.all(16), child: PrimaryButton( onPressed: _saveAudit, child: Text('Save Audit'), ).sized(width: double.infinity), ), ], ).withPadding(all: 8), ), ), ), SizedBox(height: bottomPadding), ], ), ); } Widget _buildReadOnlyRow(String label, String value) { return Padding( padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12), child: Row( children: [ Expanded( flex: 7, child: Text(label).small, ), Expanded( flex: 5, child: Text(value).small, ), ], ), ); } Widget _buildTextFieldRow({ required String label, required TextEditingController controller, int? maxLength, }) { return Padding( padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12), child: Row( children: [ Expanded( flex: 7, child: Text(label).small, ), Expanded( flex: 5, child: TextField( controller: controller, maxLength: maxLength, ), ), ], ), ); } Widget _buildTwoDropdownRow({ required String label, required int value1, required int value2, required ValueChanged onChanged1, required ValueChanged onChanged2, }) { return Padding( padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12), child: Row( children: [ Expanded( flex: 7, child: Text(label).small, ), Expanded( flex: 5, child: Row( children: [ Expanded( child: Select( itemBuilder: (context, item) => Text('$item'), value: value1, onChanged: onChanged1, popup: SelectPopup.noVirtualization( items: SelectItemList( children: [ for (int i = 0; i <= 9; i++) SelectItemButton(value: i, child: Text('$i')), ], ), ), ), ), SizedBox(width: 4), Expanded( child: Select( itemBuilder: (context, item) => Text('$item'), value: value2, onChanged: onChanged2, popup: SelectPopup.noVirtualization( items: SelectItemList( children: [ for (int i = 0; i <= 9; i++) SelectItemButton(value: i, child: Text('$i')), ], ), ), ), ), ], ), ), ], ), ); } Widget _buildDropdownRow({ required String label, required String value, required List<(String, String)> items, required ValueChanged onChanged, }) { return Padding( padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 7, child: Text(label).small, ), Expanded( flex: 5, child: Select( itemBuilder: (context, item) { final itemData = items.firstWhere((i) => i.$1 == item); return Text(itemData.$2); }, value: value, onChanged: onChanged, popup: SelectPopup.noVirtualization( items: SelectItemList( children: [ for (final item in items) SelectItemButton(value: item.$1, child: Text(item.$2)), ], ), ), ), ), ], ), ); } Widget _buildCheckboxRow({ required String label, required CheckboxState state, required ValueChanged onChanged, }) { return Padding( padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12), child: Checkbox( state: state, onChanged: onChanged, trailing: Text(label).small, ), ); } } Widget _scaffoldBgAndTing({ required BuildContext context, required Widget child, }) { Color bgColor = Colors.black; if (Theme.of(context).brightness == Brightness.light) { bgColor = Colors.white; } else { bgColor = Colors.black; } return Scaffold( child: Stack( children: [ Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage("assets/background-1.jpeg"), fit: BoxFit.cover, ), ), ), BackdropFilter( filter: ImageFilter.blur( sigmaY: 10.0, sigmaX: 10.0, ), child: Container( color: bgColor.withOpacity(0.3), ), ), Positioned.fill( child: Column( children: [ Container( height: 120, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black.withOpacity(0.8), Colors.black.withOpacity(0.5), Colors.transparent, ], ), ), ) ], ), ), child, ], ), ); }