import 'package:fbla_ui/main.dart'; import 'package:fbla_ui/shared/api_logic.dart'; import 'package:fbla_ui/shared/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../shared/global_vars.dart'; class CreateEditBusiness extends StatefulWidget { final Business? inputBusiness; const CreateEditBusiness({super.key, this.inputBusiness}); @override State createState() => _CreateEditBusinessState(); } class _CreateEditBusinessState extends State { late TextEditingController _nameController; late TextEditingController _websiteController; late TextEditingController _descriptionController; late TextEditingController _contactNameController; late TextEditingController _contactPhoneController; late TextEditingController _contactEmailController; late TextEditingController _notesController; late TextEditingController _locationNameController; late TextEditingController _locationAddressController; late bool widescreen; Business business = Business( id: 0, name: 'Business', description: 'Add details about the business below.', type: null, website: null, contactName: null, contactEmail: null, contactPhone: null, notes: null, locationName: '', locationAddress: null, ); bool _isLoading = false; String? dropDownErrorText; @override void initState() { super.initState(); if (widget.inputBusiness != null) { business = Business.copy(widget.inputBusiness!); _nameController = TextEditingController(text: business.name); _descriptionController = TextEditingController(text: business.description); business.type = widget.inputBusiness?.type; } else { _nameController = TextEditingController(); _descriptionController = TextEditingController(); } _websiteController = TextEditingController( text: business.website ?.replaceAll('https://', '') .replaceAll('http://', '') .replaceAll('www.', '')); _contactNameController = TextEditingController(text: business.contactName); _contactPhoneController = TextEditingController(text: business.contactPhone); _contactEmailController = TextEditingController(text: business.contactEmail); _notesController = TextEditingController(text: business.notes); _locationNameController = TextEditingController(text: business.locationName); _locationAddressController = TextEditingController(text: business.locationAddress); } final formKey = GlobalKey(); @override Widget build(BuildContext context) { widescreen = MediaQuery.sizeOf(context).width >= widescreenWidth; return PopScope( canPop: !_isLoading, onPopInvoked: _handlePop, child: Form( key: formKey, child: Scaffold( appBar: AppBar( title: (widget.inputBusiness != null) ? Text('Edit ${widget.inputBusiness?.name}', maxLines: 1) : const Text('Add New Business'), ), floatingActionButton: !widescreen ? FloatingActionButton.extended( heroTag: 'saveBusiness', label: const Text('Save'), icon: _isLoading ? const SizedBox( width: 24, height: 24, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 3.0, ), ) : const Icon(Icons.save), onPressed: () async { if (!_isLoading) { await _saveBusiness(context); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( width: 400, behavior: SnackBarBehavior.floating, content: Text('Please wait for it to save.'), ), ); } }, ) : null, body: ListView( children: [ Center( child: SizedBox( width: 800, child: Column( children: [ Padding( padding: const EdgeInsets.only(top: 4.0), child: Card( child: Padding( padding: const EdgeInsets.only(right: 8.0), child: ListTile( titleAlignment: ListTileTitleAlignment.titleHeight, title: Text(business.name!, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold)), subtitle: Text( business.description!, ), contentPadding: const EdgeInsets.only(bottom: 8, left: 16), leading: ClipRRect( borderRadius: BorderRadius.circular(6.0), child: Image.network( 'https://logo.clearbit.com/${business.website}', width: 48, height: 48, errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { return Icon( getIconFromBusinessType(business.type ?? BusinessType.other), size: 48); }), ), ), ), ), ), Card( child: Column( children: [ Padding( padding: const EdgeInsets.only( top: 8.0, bottom: 8.0, left: 8.0, right: 8.0), child: TextFormField( controller: _nameController, maxLength: 30, onChanged: (inputName) { setState(() { business.name = inputName; }); }, onTapOutside: (PointerDownEvent event) { FocusScope.of(context).unfocus(); }, decoration: const InputDecoration( labelText: 'Business Name (required)', ), validator: (value) { if (value != null && value.trim().isEmpty) { return 'Name is required'; } return null; }, ), ), Padding( padding: const EdgeInsets.only( bottom: 8.0, left: 8.0, right: 8.0), child: TextFormField( controller: _descriptionController, maxLength: 500, maxLines: null, onChanged: (inputDesc) { setState(() { business.description = inputDesc; }); }, onTapOutside: (PointerDownEvent event) { FocusScope.of(context).unfocus(); }, decoration: const InputDecoration( labelText: 'Business Description (required)', ), validator: (value) { if (value != null && value.trim().isEmpty) { return 'Description is required'; } return null; }, ), ), Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0, bottom: 16.0), child: TextFormField( controller: _websiteController, keyboardType: TextInputType.url, onChanged: (inputUrl) { business.website = Uri.encodeFull(inputUrl); if (inputUrl.trim().isEmpty) { business.website = null; } else { if (!business.website! .contains('http://') && !business.website! .contains('https://')) { business.website = 'https://${business.website!.trim()}'; } } }, onTapOutside: (PointerDownEvent event) { FocusScope.of(context).unfocus(); }, decoration: const InputDecoration( labelText: 'Website', ), validator: (value) { if (value != null && !RegExp(r'(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(?:/[^/\s]*)*') .hasMatch(value)) { return 'Enter a valid Website'; } return null; }, ), ), Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0, bottom: 16.0), child: DropdownMenu( initialSelection: business.type, width: (MediaQuery.sizeOf(context).width - 24) < 776 ? MediaQuery.sizeOf(context).width - 24 : 776, menuHeight: 300, label: const Text('Business Type'), errorText: dropDownErrorText, dropdownMenuEntries: [ for (BusinessType type in BusinessType.values) DropdownMenuEntry( value: type, label: getNameFromBusinessType(type)), ], onSelected: (inputType) { setState(() { business.type = inputType!; dropDownErrorText = null; }); }, ), ), ), // Padding( // padding: const EdgeInsets.only( // left: 8.0, right: 8.0, bottom: 16.0), // child: Row( // children: [ // ElevatedButton( // style: ButtonStyle( // backgroundColor: // MaterialStateProperty.all( // Theme.of(context) // .colorScheme // .background)), // child: const Row( // children: [ // Icon(Icons.search), // Text('Search For Location'), // ], // ), // onPressed: () {}, // ), // const Padding( // padding: EdgeInsets.only( // left: 32.0, right: 32.0), // child: Text( // 'OR', // style: TextStyle(fontSize: 24), // ), // ), // Expanded( // child: Column( // children: [ // TextFormField( // controller: _locationNameController, // onChanged: (inputName) { // setState(() { // business.locationName = // inputName; // }); // }, // onTapOutside: // (PointerDownEvent event) { // FocusScope.of(context).unfocus(); // }, // decoration: const InputDecoration( // labelText: // 'Location Name (optional)', // ), // ), // TextFormField( // controller: // _locationAddressController, // onChanged: (inputAddr) { // setState(() { // business.locationAddress = // inputAddr; // }); // }, // onTapOutside: // (PointerDownEvent event) { // FocusScope.of(context).unfocus(); // }, // decoration: const InputDecoration( // labelText: // 'Location Address (optional)', // ), // ), // ], // ), // ), // ], // ), // ), Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0, bottom: 16.0), child: TextFormField( controller: _contactNameController, onSaved: (inputText) { business.contactName = inputText!; }, onTapOutside: (PointerDownEvent event) { FocusScope.of(context).unfocus(); }, decoration: const InputDecoration( labelText: 'Contact Information Name (required)', ), validator: (value) { if (value == null || value.trim().isEmpty) { return 'Contact name is required'; } return null; }, ), ), Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0, bottom: 16.0), child: TextFormField( controller: _contactPhoneController, inputFormatters: [PhoneFormatter()], keyboardType: TextInputType.phone, onChanged: (inputText) { if (inputText.trim().isEmpty) { business.contactPhone = null; } else { business.contactPhone = inputText.trim(); } }, onTapOutside: (PointerDownEvent event) { FocusScope.of(context).unfocus(); }, decoration: const InputDecoration( labelText: 'Contact Phone #', ), validator: (value) { if (business.contactEmail == null && (value == null || value.isEmpty)) { return 'At least one contact method is required'; } if (value != null && value.length != 14) { return 'Enter a valid phone number'; } return null; }, ), ), Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0, bottom: 16.0), child: TextFormField( controller: _contactEmailController, keyboardType: TextInputType.emailAddress, onChanged: (inputText) { if (inputText.trim().isEmpty) { business.contactEmail = null; } else { business.contactEmail = inputText.trim(); } }, onTapOutside: (PointerDownEvent event) { FocusScope.of(context).unfocus(); }, decoration: const InputDecoration( labelText: 'Contact Email', ), validator: (value) { value = value?.trim(); if (value != null && value.isEmpty) { value = null; } if (value == null && business.contactPhone == null) { return 'At least one contact method is required'; } if (value != null) { if (!RegExp( r'^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$') .hasMatch(value)) { return 'Enter a valid Email'; } else if (value.characters.length > 50) { return 'Contact Email cannot be longer than 50 characters'; } } return null; }, ), ), Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0, bottom: 16.0), child: TextFormField( controller: _locationNameController, onChanged: (inputName) { setState(() { business.locationName = inputName.trim(); }); }, onTapOutside: (PointerDownEvent event) { FocusScope.of(context).unfocus(); }, decoration: const InputDecoration( labelText: 'Location Name (required)', ), validator: (value) { if (value != null && value.trim().isEmpty) { return 'Location name is required'; } return null; }, ), ), Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0, bottom: 16.0), child: TextFormField( controller: _locationAddressController, onChanged: (inputAddr) { setState(() { business.locationAddress = inputAddr; }); }, onTapOutside: (PointerDownEvent event) { FocusScope.of(context).unfocus(); }, decoration: const InputDecoration( labelText: 'Location Address (required)', ), validator: (value) { if (value != null && value.trim().isEmpty) { return 'Location Address is required'; } return null; }, ), ), Padding( padding: const EdgeInsets.only( left: 8.0, right: 8.0, bottom: 8.0), child: TextFormField( controller: _notesController, maxLength: 300, maxLines: null, onSaved: (inputText) { if (inputText == null || inputText.trim().isEmpty) { business.notes = null; } else { business.notes = inputText.trim(); } }, onTapOutside: (PointerDownEvent event) { FocusScope.of(context).unfocus(); }, decoration: const InputDecoration( labelText: 'Other Notes', ), ), ), ], ), ), if (!widescreen) const SizedBox( height: 75, ) else Align( alignment: Alignment.topRight, child: Padding( padding: const EdgeInsets.all(8.0), child: FilledButton( child: const Row( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: EdgeInsets.only( top: 8.0, right: 8.0, bottom: 8.0), child: Icon(Icons.save), ), Text('Save'), ], ), onPressed: () async { if (!_isLoading) { await _saveBusiness(context); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( width: 400, behavior: SnackBarBehavior.floating, content: Text('Please wait for it to save.'), ), ); } }, ), ), ) ], ), ), ), ], ), )), ); } void _handlePop(bool didPop) { if (!didPop) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( width: 400, behavior: SnackBarBehavior.floating, content: Text('Please wait for it to save.'), ), ); } } Future _saveBusiness(BuildContext context) async { if (business.type == null) { setState(() { dropDownErrorText = 'Business type is required'; }); formKey.currentState!.validate(); } else { setState(() { dropDownErrorText = null; }); if (formKey.currentState!.validate()) { formKey.currentState?.save(); setState(() { _isLoading = true; }); String? result; if (widget.inputBusiness != null) { result = await editBusiness(business); } else { result = await createBusiness(business); } setState(() { _isLoading = false; }); if (result != null) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( width: 400, behavior: SnackBarBehavior.floating, content: Text(result))); } else { Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const MainApp())); } } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('Check field inputs!'), width: 200, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), ); } } } } class PhoneFormatter extends TextInputFormatter { String phoneFormat(value) { String input = value.replaceAll(RegExp(r'[\D]'), ''); String phoneFormatted = input.isNotEmpty ? '(${input.substring(0, input.length >= 3 ? 3 : null)}${input.length >= 4 ? ') ' : ''}${input.length > 3 ? input.substring(3, input.length >= 5 ? 6 : null) + (input.length >= 7 ? '-${input.substring(6, input.length >= 10 ? 10 : null)}' : '') : ''}' : input; return phoneFormatted; } @override TextEditingValue formatEditUpdate( TextEditingValue oldValue, TextEditingValue newValue) { String text = newValue.text; if (newValue.selection.baseOffset == 0) { return newValue; } return newValue.copyWith( text: phoneFormat(text), selection: TextSelection.collapsed(offset: phoneFormat(text).length)); } }