529 lines
25 KiB
Dart
529 lines
25 KiB
Dart
import 'package:fbla_ui/api_logic.dart';
|
|
import 'package:fbla_ui/main.dart';
|
|
import 'package:fbla_ui/shared.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
class CreateEditBusiness extends StatefulWidget {
|
|
final Business? inputBusiness;
|
|
|
|
const CreateEditBusiness({super.key, this.inputBusiness});
|
|
|
|
@override
|
|
State<CreateEditBusiness> createState() => _CreateEditBusinessState();
|
|
}
|
|
|
|
class _CreateEditBusinessState extends State<CreateEditBusiness> {
|
|
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;
|
|
Business business = Business(
|
|
id: 0,
|
|
name: 'Business',
|
|
description: 'Add details about the business below.',
|
|
type: BusinessType.other,
|
|
website: '',
|
|
contactName: '',
|
|
contactEmail: '',
|
|
contactPhone: '',
|
|
notes: '',
|
|
locationName: '',
|
|
locationAddress: '',
|
|
);
|
|
bool _isLoading = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
if (widget.inputBusiness != null) {
|
|
business = Business.copy(widget.inputBusiness!);
|
|
_nameController = TextEditingController(text: business.name);
|
|
_descriptionController =
|
|
TextEditingController(text: business.description);
|
|
} else {
|
|
_nameController = TextEditingController();
|
|
_descriptionController = TextEditingController();
|
|
}
|
|
_websiteController = TextEditingController(text: business.website);
|
|
_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<FormState>();
|
|
final TextEditingController businessTypeController = TextEditingController();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
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: FloatingActionButton(
|
|
child: _isLoading
|
|
? const Padding(
|
|
padding: EdgeInsets.all(16.0),
|
|
child: CircularProgressIndicator(
|
|
color: Colors.white,
|
|
strokeWidth: 3.0,
|
|
),
|
|
)
|
|
: const Icon(Icons.save),
|
|
onPressed: () async {
|
|
if (formKey.currentState!.validate()) {
|
|
formKey.currentState?.save();
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
String? result;
|
|
// if (business.contactName == '') {
|
|
// business.contactName = 'Contact ${business.name}';
|
|
// }
|
|
if (widget.inputBusiness != null) {
|
|
result = await editBusiness(business, jwt);
|
|
} else {
|
|
result = await createBusiness(business, jwt);
|
|
}
|
|
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)),
|
|
),
|
|
);
|
|
}
|
|
},
|
|
),
|
|
body: ListView(
|
|
children: [
|
|
Center(
|
|
child: SizedBox(
|
|
width: 1000,
|
|
child: Column(
|
|
children: [
|
|
ListTile(
|
|
title: Text(business.name,
|
|
textAlign: TextAlign.left,
|
|
style: const TextStyle(
|
|
fontSize: 24, fontWeight: FontWeight.bold)),
|
|
subtitle: Text(
|
|
business.description,
|
|
textAlign: TextAlign.left,
|
|
),
|
|
leading: ClipRRect(
|
|
borderRadius: BorderRadius.circular(6.0),
|
|
child: Image.network(
|
|
width: 48,
|
|
height: 48,
|
|
'https://logo.clearbit.com/${business.website}',
|
|
errorBuilder: (BuildContext context,
|
|
Object exception, StackTrace? stackTrace) {
|
|
return getIconFromType(business.type, 48,
|
|
Theme.of(context).colorScheme.onBackground);
|
|
}),
|
|
),
|
|
),
|
|
Card(
|
|
child: Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 8.0, right: 8.0),
|
|
child: TextFormField(
|
|
controller: _nameController,
|
|
autovalidateMode:
|
|
AutovalidateMode.onUserInteraction,
|
|
maxLength: 30,
|
|
onChanged: (inputName) {
|
|
setState(() {
|
|
business.name = inputName;
|
|
});
|
|
},
|
|
onTapOutside: (PointerDownEvent event) {
|
|
FocusScope.of(context).unfocus();
|
|
},
|
|
decoration: const InputDecoration(
|
|
labelText: 'Business Name',
|
|
),
|
|
validator: (value) {
|
|
if (value != null && value.isEmpty) {
|
|
return 'Name is required';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 8.0, right: 8.0, bottom: 8.0),
|
|
child: TextFormField(
|
|
controller: _websiteController,
|
|
autovalidateMode:
|
|
AutovalidateMode.onUserInteraction,
|
|
keyboardType: TextInputType.url,
|
|
onChanged: (inputUrl) {
|
|
business.website = Uri.encodeFull(inputUrl
|
|
.toLowerCase()
|
|
.replaceAll('https://', '')
|
|
.replaceAll('http://', '')
|
|
.replaceAll('www.', ''));
|
|
},
|
|
onTapOutside: (PointerDownEvent event) {
|
|
FocusScope.of(context).unfocus();
|
|
},
|
|
decoration: const InputDecoration(
|
|
labelText: 'Website',
|
|
),
|
|
validator: (value) {
|
|
if (value != null && value.isEmpty) {
|
|
return 'Website is required';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 8.0, right: 8.0),
|
|
child: TextFormField(
|
|
controller: _descriptionController,
|
|
autovalidateMode:
|
|
AutovalidateMode.onUserInteraction,
|
|
maxLength: 500,
|
|
onChanged: (inputDesc) {
|
|
setState(() {
|
|
business.description = inputDesc;
|
|
});
|
|
},
|
|
onTapOutside: (PointerDownEvent event) {
|
|
FocusScope.of(context).unfocus();
|
|
},
|
|
decoration: const InputDecoration(
|
|
labelText: 'Business Description',
|
|
),
|
|
validator: (value) {
|
|
if (value != null && value.isEmpty) {
|
|
return 'Description is required';
|
|
}
|
|
return 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: 8.0),
|
|
child: TextFormField(
|
|
controller: _locationNameController,
|
|
onChanged: (inputName) {
|
|
setState(() {
|
|
business.locationName = inputName;
|
|
});
|
|
},
|
|
onTapOutside: (PointerDownEvent event) {
|
|
FocusScope.of(context).unfocus();
|
|
},
|
|
decoration: const InputDecoration(
|
|
labelText: 'Location Name (optional)',
|
|
),
|
|
),
|
|
),
|
|
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 (optional)',
|
|
),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 8.0, right: 8.0, bottom: 8.0),
|
|
child: Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text('Type of Business',
|
|
style: TextStyle(fontSize: 16)),
|
|
DropdownMenu<BusinessType>(
|
|
initialSelection: business.type,
|
|
controller: businessTypeController,
|
|
label: const Text('Business Type'),
|
|
dropdownMenuEntries: const [
|
|
DropdownMenuEntry(
|
|
value: BusinessType.food,
|
|
label: 'Food Related'),
|
|
DropdownMenuEntry(
|
|
value: BusinessType.shop,
|
|
label: 'Shop'),
|
|
DropdownMenuEntry(
|
|
value: BusinessType.outdoors,
|
|
label: 'Outdoors'),
|
|
DropdownMenuEntry(
|
|
value: BusinessType.manufacturing,
|
|
label: 'Manufacturing'),
|
|
DropdownMenuEntry(
|
|
value: BusinessType.other,
|
|
label: 'Other'),
|
|
],
|
|
onSelected: (inputType) {
|
|
business.type = inputType!;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 8.0, right: 8.0, bottom: 8.0),
|
|
child: TextFormField(
|
|
controller: _contactNameController,
|
|
onSaved: (inputText) {
|
|
business.contactName = inputText!;
|
|
},
|
|
onTapOutside: (PointerDownEvent event) {
|
|
FocusScope.of(context).unfocus();
|
|
},
|
|
decoration: const InputDecoration(
|
|
labelText:
|
|
'Contact Information Name (optional)',
|
|
),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 8.0, right: 8.0, bottom: 8.0),
|
|
child: TextFormField(
|
|
controller: _contactPhoneController,
|
|
inputFormatters: [PhoneFormatter()],
|
|
keyboardType: TextInputType.phone,
|
|
onSaved: (inputText) {
|
|
business.contactPhone = inputText!;
|
|
},
|
|
onTapOutside: (PointerDownEvent event) {
|
|
FocusScope.of(context).unfocus();
|
|
},
|
|
decoration: const InputDecoration(
|
|
labelText: 'Contact Phone # (optional)',
|
|
),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 8.0, right: 8.0, bottom: 8.0),
|
|
child: TextFormField(
|
|
controller: _contactEmailController,
|
|
keyboardType: TextInputType.emailAddress,
|
|
onSaved: (inputText) {
|
|
business.contactEmail = inputText!;
|
|
},
|
|
onTapOutside: (PointerDownEvent event) {
|
|
FocusScope.of(context).unfocus();
|
|
},
|
|
decoration: const InputDecoration(
|
|
labelText: 'Contact Email (optional)',
|
|
),
|
|
validator: (value) {
|
|
if (value != null) {
|
|
if (value.isEmpty) {
|
|
return null;
|
|
} else 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: 8.0),
|
|
child: TextFormField(
|
|
controller: _notesController,
|
|
maxLength: 300,
|
|
onSaved: (inputText) {
|
|
business.notes = inputText!;
|
|
},
|
|
onTapOutside: (PointerDownEvent event) {
|
|
FocusScope.of(context).unfocus();
|
|
},
|
|
decoration: const InputDecoration(
|
|
labelText: 'Other Notes (optional)',
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(
|
|
height: 75,
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)),
|
|
);
|
|
}
|
|
|
|
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.'),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|