import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import '../providers/customers_provider.dart'; import '../domain/customer.dart'; import 'dart:async'; import 'dart:io'; import 'package:image_picker/image_picker.dart'; import '../../../core/network/dio_client.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart' as import_storage; import '../../../core/widgets/premium_text_field.dart'; import '../../../core/widgets/smart_search_dropdown.dart'; import '../../business/providers/indian_states_provider.dart'; class AddCustomerSheet extends ConsumerStatefulWidget { final Customer? customer; const AddCustomerSheet({super.key, this.customer}); @override ConsumerState createState() => _AddCustomerSheetState(); } class _AddCustomerSheetState extends ConsumerState { final _formKey = GlobalKey(); late TextEditingController _nameCtrl; late TextEditingController _phoneCtrl; late TextEditingController _emailCtrl; late TextEditingController _addressCtrl; late TextEditingController _gstinCtrl; late TextEditingController _idNumberCtrl; late TextEditingController _fatherNameCtrl; late TextEditingController _ageCtrl; String? _selectedGender; int? _selectedStateId; XFile? _photo; String? _token; bool _isLoading = false; @override void initState() { super.initState(); _nameCtrl = TextEditingController(text: widget.customer?.name ?? ''); _phoneCtrl = TextEditingController(text: widget.customer?.phone ?? ''); _emailCtrl = TextEditingController(text: widget.customer?.email ?? ''); _addressCtrl = TextEditingController(text: widget.customer?.address ?? ''); _gstinCtrl = TextEditingController(text: widget.customer?.gstin ?? ''); _idNumberCtrl = TextEditingController( text: widget.customer?.idNumber ?? '', ); _fatherNameCtrl = TextEditingController( text: widget.customer?.fatherName ?? '', ); _ageCtrl = TextEditingController( text: widget.customer?.age?.toString() ?? '', ); _selectedGender = widget.customer?.gender; _selectedStateId = widget.customer?.stateId; import_storage.FlutterSecureStorage().read(key: 'jwt_token').then((val) { if (mounted) setState(() => _token = val); }); } @override void dispose() { _nameCtrl.dispose(); _phoneCtrl.dispose(); _emailCtrl.dispose(); _addressCtrl.dispose(); _gstinCtrl.dispose(); _idNumberCtrl.dispose(); _fatherNameCtrl.dispose(); _ageCtrl.dispose(); super.dispose(); } Future _pickImage(ImageSource source) async { final picker = ImagePicker(); final picked = await picker.pickImage(source: source, imageQuality: 80); if (picked != null) { setState(() => _photo = picked); } } void _showImagePickerModal() { showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (ctx) => SafeArea( child: Wrap( children: [ ListTile( leading: const Icon(LucideIcons.camera), title: const Text('Take a photo'), onTap: () { Navigator.pop(ctx); _pickImage(ImageSource.camera); }, ), ListTile( leading: const Icon(LucideIcons.image), title: const Text('Choose from gallery'), onTap: () { Navigator.pop(ctx); _pickImage(ImageSource.gallery); }, ), ], ), ), ); } Future _save() async { if (!_formKey.currentState!.validate()) return; setState(() => _isLoading = true); final customer = Customer( id: widget.customer?.id, name: _nameCtrl.text, phone: _phoneCtrl.text.isNotEmpty ? _phoneCtrl.text : null, email: _emailCtrl.text.isNotEmpty ? _emailCtrl.text : null, address: _addressCtrl.text.isNotEmpty ? _addressCtrl.text : null, gstin: _gstinCtrl.text.isNotEmpty ? _gstinCtrl.text : null, idNumber: _idNumberCtrl.text.isNotEmpty ? _idNumberCtrl.text : null, fatherName: _fatherNameCtrl.text.isNotEmpty ? _fatherNameCtrl.text : null, age: int.tryParse(_ageCtrl.text), gender: _selectedGender, stateId: _selectedStateId, ); try { if (widget.customer == null) { await ref .read(customersProvider.notifier) .addCustomer(customer, photo: _photo); } else { await ref .read(customersProvider.notifier) .updateCustomer(widget.customer!.id!, customer, photo: _photo); } if (mounted) { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( widget.customer == null ? 'Customer added' : 'Customer updated', ), ), ); } } catch (e) { if (mounted) { showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Error'), content: Text(e.toString()), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('OK'), ), ], ), ); } } finally { if (mounted) setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { return Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, left: 24, right: 24, top: 48, ), child: Form( key: _formKey, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ IconButton( icon: const Icon(LucideIcons.chevronLeft, size: 28), padding: EdgeInsets.zero, constraints: const BoxConstraints(), onPressed: () => Navigator.pop(context), ), const SizedBox(width: 12), Text( widget.customer == null ? "New Customer" : "Edit Customer", style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, ), ), ], ), const SizedBox(height: 24), Center( child: GestureDetector( onTap: _showImagePickerModal, child: Stack( children: [ CircleAvatar( radius: 50, backgroundColor: Colors.grey[200], backgroundImage: _photo != null ? (kIsWeb ? NetworkImage(_photo!.path) : FileImage(File(_photo!.path))) as ImageProvider : (widget.customer?.photoUrl != null && _token != null ? NetworkImage( '${DioClient().dio.options.baseUrl}/customers/${widget.customer!.id}/photo/content', headers: { 'Authorization': 'Bearer $_token', }, ) : null), child: _photo == null && widget.customer?.photoUrl == null ? const Icon( LucideIcons.user, size: 50, color: Colors.grey, ) : null, ), Positioned( bottom: 0, right: 0, child: Container( padding: const EdgeInsets.all(8), decoration: const BoxDecoration( color: Colors.blue, shape: BoxShape.circle, ), child: const Icon( LucideIcons.camera, color: Colors.white, size: 16, ), ), ), ], ), ), ), const SizedBox(height: 24), PremiumTextField( controller: _nameCtrl, labelText: 'Customer Name *', prefixIcon: const Icon(LucideIcons.user), validator: (val) => val == null || val.isEmpty ? 'Required' : null, ), const SizedBox(height: 16), PremiumTextField( controller: _phoneCtrl, labelText: 'Phone Number', prefixIcon: const Icon(LucideIcons.phone), keyboardType: TextInputType.phone, ), const SizedBox(height: 16), PremiumTextField( controller: _fatherNameCtrl, labelText: 'Father Name', prefixIcon: const Icon(LucideIcons.users), ), const SizedBox(height: 16), Row( children: [ Expanded( child: PremiumTextField( controller: _ageCtrl, labelText: 'Age', prefixIcon: const Icon(LucideIcons.calendar), keyboardType: TextInputType.number, ), ), const SizedBox(width: 16), Expanded( child: Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 4, ), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(16), ), child: DropdownButtonHideUnderline( child: DropdownButton( value: _selectedGender, isExpanded: true, hint: const Text('Gender'), items: ['Male', 'Female', 'Other'] .map( (g) => DropdownMenuItem(value: g, child: Text(g)), ) .toList(), onChanged: (val) => setState(() => _selectedGender = val), ), ), ), ), ], ), const SizedBox(height: 16), PremiumTextField( controller: _emailCtrl, labelText: 'Email Address', prefixIcon: const Icon(LucideIcons.mail), keyboardType: TextInputType.emailAddress, ), const SizedBox(height: 16), PremiumTextField( controller: _gstinCtrl, labelText: 'GSTIN (Optional)', prefixIcon: const Icon(LucideIcons.building), textCapitalization: TextCapitalization.characters, validator: (val) { if (val == null || val.isEmpty) return null; // Optional final RegExp gstRegExp = RegExp( r'^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$', ); if (!gstRegExp.hasMatch(val.toUpperCase())) { return 'Invalid GSTIN format'; } return null; }, ), const SizedBox(height: 16), PremiumTextField( controller: _idNumberCtrl, labelText: 'ID No. (Aadhar, License, etc.)', prefixIcon: const Icon(LucideIcons.creditCard), textCapitalization: TextCapitalization.characters, ), const SizedBox(height: 16), Consumer( builder: (context, ref, _) { final statesState = ref.watch(indianStatesProvider); return statesState.when( data: (states) { return SmartSearchDropdown( labelText: 'State', hintText: 'Select State', value: _selectedStateId, items: states.map((s) => s.id).toList(), itemAsString: (id) { final s = states.where((st) => st.id == id).firstOrNull; if (s == null) return 'Unknown'; return '${s.name} (${s.gstCode})'; }, onChanged: (val) => setState(() => _selectedStateId = val), ); }, loading: () => const CircularProgressIndicator(), error: (e, stack) => Text('Error loading states: $e'), ); }, ), const SizedBox(height: 16), PremiumTextField( controller: _addressCtrl, labelText: 'Billing Address', prefixIcon: const Icon(LucideIcons.mapPin), maxLines: 3, ), const SizedBox(height: 24), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _isLoading ? null : _save, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: Colors.blue, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: _isLoading ? const SizedBox( width: 24, height: 24, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ), ) : Text( widget.customer == null ? 'Save Customer' : 'Update Customer', ), ), ), const SizedBox(height: 24), ], ), ), ), ); } }