import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/resources/drivers_model.dart'; class ResourcesDriverScreen extends StatefulWidget { const ResourcesDriverScreen({super.key}); @override State createState() => _ResourcesDriverScreenState(); } class _ResourcesDriverScreenState extends State { // ---------- screen state ---------- int selectedTab = 1; // default "Drivers" String search = ''; bool isLoading = false; List driversList = []; // ---------- form (bottom sheet) ---------- final GlobalKey _formKey = GlobalKey(); // Text controllers final _nameCtrl = TextEditingController(); final _mobileCtrl = TextEditingController(); final _altMobileCtrl = TextEditingController(); final _locationCtrl = TextEditingController(); // Unused in UI but kept if you later need them final _commissionCtrl = TextEditingController(); final _joinDateCtrl = TextEditingController(); // Dropdown state String? _status; // 'available' | 'on delivery' | 'offline' final List _statusOptions = const ['available', 'on delivery', 'offline']; String? selectedLicense; final List licenseNumbers = const [ 'DL-042019-9876543', 'DL-052020-1234567', 'DL-072021-7654321', ]; String? selectedExperience; // years as string final List yearOptions = List.generate(41, (i) => '$i'); // 0..40 String? _required(String? v, {String field = "This field"}) { if (v == null || v.trim().isEmpty) return "$field is required"; return null; } String? _validatePhone(String? v, {String label = "Phone"}) { if (v == null || v.trim().isEmpty) return "$label is required"; if (v.trim().length != 10) return "$label must be 10 digits"; return null; } @override void initState() { super.initState(); _fetchDrivers(); } @override void dispose() { _nameCtrl.dispose(); _mobileCtrl.dispose(); _altMobileCtrl.dispose(); _locationCtrl.dispose(); _commissionCtrl.dispose(); _joinDateCtrl.dispose(); super.dispose(); } void _resetForm() { _formKey.currentState?.reset(); _nameCtrl.clear(); _mobileCtrl.clear(); _altMobileCtrl.clear(); _locationCtrl.clear(); _commissionCtrl.clear(); _joinDateCtrl.clear(); selectedLicense = null; selectedExperience = null; _status = null; } Future _fetchDrivers() async { setState(() => isLoading = true); try { final response = await AppSettings.getDrivers(); final data = (jsonDecode(response)['data'] as List) .map((e) => DriversModel.fromJson(e)) .toList(); if (!mounted) return; setState(() { driversList = data; isLoading = false; }); } catch (e) { debugPrint("⚠️ Error fetching drivers: $e"); setState(() => isLoading = false); } } // ---------- submit ---------- Future _addDriver() async { // run all validators, including the dropdowns final ok = _formKey.currentState?.validate() ?? false; if (!ok) { setState(() {}); // ensure error texts render return; } // Build payload (adjust keys to your API if needed) final payload = { "Name": _nameCtrl.text.trim(), "license_number": selectedLicense ?? "", "address": _locationCtrl.text.trim().isEmpty ? AppSettings.userAddress : _locationCtrl.text.trim(), "supplier_name": AppSettings.userName, "phone": _mobileCtrl.text.trim(), "alternativeContactNumber": _altMobileCtrl.text.trim(), "years_of_experience": selectedExperience ?? "", "status": _status ?? "available", }; try { final bool created = await AppSettings.addDrivers(payload); if (!mounted) return; if (created) { AppSettings.longSuccessToast("Driver created successfully"); Navigator.pop(context, true); // close sheet _resetForm(); _fetchDrivers(); // refresh } else { AppSettings.longFailedToast("Driver creation failed"); } } catch (e) { debugPrint("⚠️ addDrivers error: $e"); if (!mounted) return; AppSettings.longFailedToast("Something went wrong"); } } // ---------- bottom sheet ---------- void _openDriverFormSheet(BuildContext context) { _resetForm(); // fresh form every time showModalBottomSheet( context: context, isScrollControlled: true, // allow content to move for keyboard backgroundColor: Colors.transparent, // style inner container builder: (context) { final bottomInset = MediaQuery.of(context).viewInsets.bottom; return FractionallySizedBox( heightFactor: 0.75, // fixed height (75% of screen) child: Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Padding( padding: EdgeInsets.fromLTRB(20, 16, 20, 20 + bottomInset), child: Form( key: _formKey, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Handle + close (optional, simple close icon) Row( children: [ Expanded( child: Center( child: Container( width: 36, height: 4, margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: const Color(0xFFE0E0E0), borderRadius: BorderRadius.circular(2), ), ), ), ), IconButton( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close), ), ], ), _LabeledField( label: "Driver Name *", child: TextFormField( controller: _nameCtrl, validator: (v) => _required(v, field: "Driver Name"), decoration: InputDecoration( hintText: "Full Name", hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), border: const OutlineInputBorder(), isDense: true, ), textInputAction: TextInputAction.next, ), ), _LabeledField( label: "Driver License Number *", child: DropdownButtonFormField( value: selectedLicense, items: licenseNumbers .map((t) => DropdownMenuItem(value: t, child: Text(t))) .toList(), onChanged: (v) => setState(() => selectedLicense = v), validator: (v) => v == null || v.isEmpty ? "Driver License required" : null, isExpanded: true, alignment: Alignment.centerLeft, hint: Text( "Select License Number", style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), ), icon: Image.asset('images/downarrow.png', width: 16, height: 16), decoration: const InputDecoration( border: OutlineInputBorder(), isDense: false, contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14), ), ), ), _LabeledField( label: "Years of Experience *", child: DropdownButtonFormField( value: selectedExperience, items: yearOptions .map((t) => DropdownMenuItem(value: t, child: Text(t))) .toList(), onChanged: (v) => setState(() => selectedExperience = v), validator: (v) => v == null || v.isEmpty ? "Experience is required" : null, isExpanded: true, alignment: Alignment.centerLeft, hint: Text( "Years", style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), ), icon: Image.asset('images/downarrow.png', width: 16, height: 16), decoration: const InputDecoration( border: OutlineInputBorder(), isDense: false, contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14), ), ), ), _LabeledField( label: "Phone Number *", child: TextFormField( controller: _mobileCtrl, validator: (v) => _validatePhone(v, label: "Phone Number"), keyboardType: TextInputType.phone, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10), ], decoration: InputDecoration( hintText: "Mobile Number", hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), border: const OutlineInputBorder(), isDense: true, ), textInputAction: TextInputAction.next, ), ), _LabeledField( label: "Alternate Phone Number", child: TextFormField( controller: _altMobileCtrl, validator: (v) { if (v == null || v.trim().isEmpty) return null; // optional return _validatePhone(v, label: "Alternate Phone Number"); }, keyboardType: TextInputType.phone, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10), ], decoration: InputDecoration( hintText: "Mobile Number", hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), border: const OutlineInputBorder(), isDense: true, ), textInputAction: TextInputAction.next, ), ), _LabeledField( label: "Location *", child: TextFormField( controller: _locationCtrl, validator: (v) => _required(v, field: "Location"), decoration: InputDecoration( hintText: "Area / locality", hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), border: const OutlineInputBorder(), isDense: true, ), textInputAction: TextInputAction.done, ), ), _LabeledField( label: "Status *", child: DropdownButtonFormField( value: _status, items: _statusOptions .map((s) => DropdownMenuItem(value: s, child: Text(s))) .toList(), onChanged: (v) => setState(() => _status = v), validator: (v) => v == null || v.isEmpty ? "Status is required" : null, isExpanded: true, alignment: Alignment.centerLeft, hint: Text( "Select status", style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400), ), icon: const Icon(Icons.keyboard_arrow_down_rounded), decoration: const InputDecoration( border: OutlineInputBorder(), isDense: false, contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14), ), ), ), const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF8270DB), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), ), onPressed: _addDriver, child: Text( "Save", style: fontTextStyle(14, Colors.white, FontWeight.w600), ), ), ), ], ), ), ), ), ), ); }, ); } // ---------- UI ---------- @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: Column( children: [ // Top card Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFF939495)), ), child: Row( children: [ Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 40, height: 40, decoration: const BoxDecoration( color: Color(0xFFF6F0FF), borderRadius: BorderRadius.all(Radius.circular(8)), ), child: Padding( padding: const EdgeInsets.all(8.0), child: Image.asset('images/drivers.png', fit: BoxFit.contain), ), ), const SizedBox(height: 8), Text('Total Drivers', style: fontTextStyle(12, const Color(0xFF2D2E30), FontWeight.w500)), ], ), const Spacer(), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text(driversList.length.toString(), style: fontTextStyle(24, const Color(0xFF0D3771), FontWeight.w500)), const SizedBox(height: 6), Text('+1 since last month', style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400)), ], ), ], ), ), // Metrics row Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: IntrinsicHeight( child: Row( children: const [ Expanded(child: SmallMetricBox(title: 'On delivery', value: '2')), SizedBox(width: 8), Expanded(child: SmallMetricBox(title: 'Available', value: '3')), SizedBox(width: 8), Expanded(child: SmallMetricBox(title: 'Offline', value: '1')), ], ), ), ), const SizedBox(height: 12), // Search + list Expanded( child: Container( color: const Color(0xFFF5F5F5), child: Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), child: Column( children: [ Row( children: [ Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( border: Border.all(color: const Color(0xFF939495), width: 0.5), borderRadius: BorderRadius.circular(22), ), child: Row( children: [ Image.asset('images/search.png', width: 18, height: 18), const SizedBox(width: 8), Expanded( child: TextField( decoration: InputDecoration( hintText: 'Search', hintStyle: fontTextStyle(12, const Color(0xFF939495), FontWeight.w400), border: InputBorder.none, isDense: true, ), onChanged: (v) => setState(() => search = v), ), ), ], ), ), ), const SizedBox(width: 16), Image.asset("images/icon_tune.png", width: 24, height: 24), const SizedBox(width: 16), Image.asset("images/up_down arrow.png", width: 24, height: 24), ], ), const SizedBox(height: 12), Expanded( child: isLoading ? const Center(child: CircularProgressIndicator()) : ListView.separated( itemCount: driversList.length, separatorBuilder: (_, __) => const SizedBox(height: 12), itemBuilder: (context, idx) { final d = driversList[idx]; return DriverCard( name: d.driver_name, status: d.status, location: d.address, deliveries: int.tryParse(d.deliveries) ?? 0, commission: d.commision, ); }, ), ), ], ), ), ), ), ], ), // FAB -> open fixed-height form sheet floatingActionButton: FloatingActionButton( onPressed: () => _openDriverFormSheet(context), backgroundColor: Colors.black, shape: const CircleBorder(), child: const Icon(Icons.add, color: Colors.white), ), ); } } // ---------- widgets ---------- class SmallMetricBox extends StatelessWidget { final String title; final String value; const SmallMetricBox({super.key, required this.title, required this.value}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFF939495)), color: Colors.white, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: fontTextStyle(12, const Color(0xFF2D2E30), FontWeight.w500)), const SizedBox(height: 4), Text(value, style: fontTextStyle(24, const Color(0xFF0D3771), FontWeight.w500)), ], ), ); } } class DriverCard extends StatelessWidget { final String name; final String status; final String location; final int deliveries; final String commission; const DriverCard({ super.key, required this.name, required this.status, required this.location, required this.deliveries, required this.commission, }); @override Widget build(BuildContext context) { final statusColor = status == "available" ? Colors.green : (status == "on delivery" ? Colors.orange : Colors.grey); return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade300), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Row with name + phone Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ ClipOval( child: Image.asset("images/profile_pic.png", height: 36, width: 36, fit: BoxFit.cover), ), const SizedBox(width: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(name, style: fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w500)), const SizedBox(height: 4), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), border: Border.all(color: statusColor), ), child: Text(status, style: fontTextStyle(10, statusColor, FontWeight.w400)), ), ], ), ], ), Container( padding: const EdgeInsets.all(8), decoration: const BoxDecoration( color: Color(0xFFF5F6F6), shape: BoxShape.circle, ), child: Image.asset("images/phone_icon.png", width: 20, height: 20), ), ], ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text.rich( TextSpan( children: [ TextSpan(text: "Location\n", style: fontTextStyle(8, const Color(0xFF939495), FontWeight.w500)), TextSpan(text: location, style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w500)), ], ), textAlign: TextAlign.center, ), Text.rich( TextSpan( children: [ TextSpan(text: "Deliveries\n", style: fontTextStyle(8, const Color(0xFF939495), FontWeight.w400)), TextSpan(text: deliveries.toString(), style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w500)), ], ), textAlign: TextAlign.center, ), Text.rich( TextSpan( children: [ TextSpan(text: "Commission\n", style: fontTextStyle(8, const Color(0xFF939495), FontWeight.w400)), TextSpan(text: commission, style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w500)), ], ), textAlign: TextAlign.center, ), ], ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ OutlinedButton( style: OutlinedButton.styleFrom( side: const BorderSide(color: Color(0xFF939495)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), ), onPressed: () {}, child: Text("View Schedule", style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w400)), ), const SizedBox(width: 12), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF8270DB), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), ), onPressed: () {}, child: Text("Assign", style: fontTextStyle(12, const Color(0xFFFFFFFF), FontWeight.w400)), ), ], ) ], ), ); } } class _LabeledField extends StatelessWidget { final String label; final Widget child; const _LabeledField({required this.label, required this.child}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 14.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w600)), const SizedBox(height: 6), child, ], ), ); } }