feat(contact): enhance AddContact functionality with validation and initialization state
This commit is contained in:
parent
445cd75e03
commit
77e27ff98e
@ -24,6 +24,7 @@ class AddContactController extends GetxController {
|
||||
final RxMap<String, String> bucketsMap = <String, String>{}.obs;
|
||||
final RxMap<String, String> projectsMap = <String, String>{}.obs;
|
||||
final RxMap<String, String> tagsMap = <String, String>{}.obs;
|
||||
final RxBool isInitialized = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -41,6 +42,9 @@ class AddContactController extends GetxController {
|
||||
fetchCategories(),
|
||||
fetchOrganizationNames(),
|
||||
]);
|
||||
|
||||
// ✅ Mark initialization as done
|
||||
isInitialized.value = true;
|
||||
}
|
||||
|
||||
void resetForm() {
|
||||
@ -90,11 +94,103 @@ class AddContactController extends GetxController {
|
||||
required String address,
|
||||
required String description,
|
||||
}) async {
|
||||
try {
|
||||
final categoryId = categoriesMap[selectedCategory.value];
|
||||
final bucketId = bucketsMap[selectedBucket.value];
|
||||
final projectId = projectsMap[selectedProject.value];
|
||||
final categoryId = categoriesMap[selectedCategory.value];
|
||||
final bucketId = bucketsMap[selectedBucket.value];
|
||||
final projectId = projectsMap[selectedProject.value];
|
||||
|
||||
// === Per-field Validation with Specific Messages ===
|
||||
if (name.trim().isEmpty) {
|
||||
showAppSnackbar(
|
||||
title: "Missing Name",
|
||||
message: "Please enter the contact name.",
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (organization.trim().isEmpty) {
|
||||
showAppSnackbar(
|
||||
title: "Missing Organization",
|
||||
message: "Please enter the organization name.",
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (emails.isEmpty) {
|
||||
showAppSnackbar(
|
||||
title: "Missing Email",
|
||||
message: "Please add at least one email.",
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (phones.isEmpty) {
|
||||
showAppSnackbar(
|
||||
title: "Missing Phone Number",
|
||||
message: "Please add at least one phone number.",
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (address.trim().isEmpty) {
|
||||
showAppSnackbar(
|
||||
title: "Missing Address",
|
||||
message: "Please enter the address.",
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (description.trim().isEmpty) {
|
||||
showAppSnackbar(
|
||||
title: "Missing Description",
|
||||
message: "Please enter a description.",
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedCategory.value.trim().isEmpty || categoryId == null) {
|
||||
showAppSnackbar(
|
||||
title: "Missing Category",
|
||||
message: "Please select a contact category.",
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedBucket.value.trim().isEmpty || bucketId == null) {
|
||||
showAppSnackbar(
|
||||
title: "Missing Bucket",
|
||||
message: "Please select a bucket.",
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedProject.value.trim().isEmpty || projectId == null) {
|
||||
showAppSnackbar(
|
||||
title: "Missing Project",
|
||||
message: "Please select a project.",
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (enteredTags.isEmpty) {
|
||||
showAppSnackbar(
|
||||
title: "Missing Tags",
|
||||
message: "Please enter at least one tag.",
|
||||
type: SnackbarType.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// === Submit if all validations passed ===
|
||||
try {
|
||||
final tagObjects = enteredTags.map((tagName) {
|
||||
final tagId = tagsMap[tagName];
|
||||
return tagId != null
|
||||
@ -104,16 +200,16 @@ class AddContactController extends GetxController {
|
||||
|
||||
final body = {
|
||||
if (id != null) "id": id,
|
||||
"name": name,
|
||||
"organization": organization,
|
||||
"name": name.trim(),
|
||||
"organization": organization.trim(),
|
||||
"contactCategoryId": categoryId,
|
||||
"projectIds": projectId != null ? [projectId] : [],
|
||||
"bucketIds": bucketId != null ? [bucketId] : [],
|
||||
"projectIds": [projectId],
|
||||
"bucketIds": [bucketId],
|
||||
"tags": tagObjects,
|
||||
"contactEmails": emails,
|
||||
"contactPhones": phones,
|
||||
"address": address,
|
||||
"description": description,
|
||||
"address": address.trim(),
|
||||
"description": description.trim(),
|
||||
};
|
||||
|
||||
logSafe("${id != null ? 'Updating' : 'Creating'} contact");
|
||||
|
@ -1,90 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:marco/controller/directory/add_contact_controller.dart';
|
||||
import 'package:marco/helpers/widgets/my_spacing.dart';
|
||||
import 'package:marco/helpers/widgets/my_text.dart';
|
||||
import 'package:marco/helpers/widgets/my_text_style.dart';
|
||||
import 'package:marco/model/directory/contact_model.dart';
|
||||
|
||||
class AddContactBottomSheet extends StatelessWidget {
|
||||
class AddContactBottomSheet extends StatefulWidget {
|
||||
final ContactModel? existingContact;
|
||||
const AddContactBottomSheet({super.key, this.existingContact});
|
||||
|
||||
AddContactBottomSheet({super.key, this.existingContact}) {
|
||||
controller.resetForm();
|
||||
|
||||
nameController.text = existingContact?.name ?? '';
|
||||
orgController.text = existingContact?.organization ?? '';
|
||||
tagTextController.clear();
|
||||
addressController.text = existingContact?.address ?? '';
|
||||
descriptionController.text = existingContact?.description ?? '';
|
||||
|
||||
if (existingContact != null) {
|
||||
emailControllers.clear();
|
||||
emailLabels.clear();
|
||||
for (var email in existingContact!.contactEmails) {
|
||||
emailControllers.add(TextEditingController(text: email.emailAddress));
|
||||
emailLabels.add((email.label ?? 'Office').obs);
|
||||
}
|
||||
|
||||
if (emailControllers.isEmpty) {
|
||||
emailControllers.add(TextEditingController());
|
||||
emailLabels.add('Office'.obs);
|
||||
}
|
||||
|
||||
phoneControllers.clear();
|
||||
phoneLabels.clear();
|
||||
for (var phone in existingContact!.contactPhones) {
|
||||
phoneControllers.add(TextEditingController(text: phone.phoneNumber));
|
||||
phoneLabels.add((phone.label ?? 'Work').obs);
|
||||
}
|
||||
|
||||
if (phoneControllers.isEmpty) {
|
||||
phoneControllers.add(TextEditingController());
|
||||
phoneLabels.add('Work'.obs);
|
||||
}
|
||||
|
||||
controller.selectedCategory.value =
|
||||
existingContact!.contactCategory?.name ?? '';
|
||||
|
||||
if (existingContact!.projectIds?.isNotEmpty == true) {
|
||||
controller.selectedProject.value = controller.globalProjects
|
||||
.firstWhereOrNull(
|
||||
(e) => e == existingContact!.projectIds!.first,
|
||||
)
|
||||
?.toString() ??
|
||||
'';
|
||||
}
|
||||
|
||||
if (existingContact!.bucketIds.isNotEmpty) {
|
||||
controller.selectedBucket.value = controller.buckets
|
||||
.firstWhereOrNull(
|
||||
(b) => b == existingContact!.bucketIds.first,
|
||||
)
|
||||
?.toString() ??
|
||||
'';
|
||||
}
|
||||
|
||||
controller.enteredTags.assignAll(
|
||||
existingContact!.tags.map((tag) => tag.name).toList(),
|
||||
);
|
||||
} else {
|
||||
emailControllers.add(TextEditingController());
|
||||
emailLabels.add('Office'.obs);
|
||||
|
||||
phoneControllers.add(TextEditingController());
|
||||
phoneLabels.add('Work'.obs);
|
||||
}
|
||||
}
|
||||
@override
|
||||
State<AddContactBottomSheet> createState() => _AddContactBottomSheetState();
|
||||
}
|
||||
|
||||
class _AddContactBottomSheetState extends State<AddContactBottomSheet> {
|
||||
final controller = Get.put(AddContactController());
|
||||
final formKey = GlobalKey<FormState>();
|
||||
|
||||
final nameController = TextEditingController();
|
||||
final orgController = TextEditingController();
|
||||
final tagTextController = TextEditingController();
|
||||
final addressController = TextEditingController();
|
||||
final descriptionController = TextEditingController();
|
||||
final tagTextController = TextEditingController();
|
||||
|
||||
final RxList<TextEditingController> emailControllers =
|
||||
<TextEditingController>[].obs;
|
||||
@ -94,6 +34,94 @@ class AddContactBottomSheet extends StatelessWidget {
|
||||
<TextEditingController>[].obs;
|
||||
final RxList<RxString> phoneLabels = <RxString>[].obs;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller.resetForm();
|
||||
|
||||
nameController.text = widget.existingContact?.name ?? '';
|
||||
orgController.text = widget.existingContact?.organization ?? '';
|
||||
addressController.text = widget.existingContact?.address ?? '';
|
||||
descriptionController.text = widget.existingContact?.description ?? '';
|
||||
tagTextController.clear();
|
||||
|
||||
if (widget.existingContact != null) {
|
||||
emailControllers.clear();
|
||||
emailLabels.clear();
|
||||
for (var email in widget.existingContact!.contactEmails) {
|
||||
emailControllers.add(TextEditingController(text: email.emailAddress));
|
||||
emailLabels.add((email.label).obs);
|
||||
}
|
||||
if (emailControllers.isEmpty) {
|
||||
emailControllers.add(TextEditingController());
|
||||
emailLabels.add('Office'.obs);
|
||||
}
|
||||
|
||||
phoneControllers.clear();
|
||||
phoneLabels.clear();
|
||||
for (var phone in widget.existingContact!.contactPhones) {
|
||||
phoneControllers.add(TextEditingController(text: phone.phoneNumber));
|
||||
phoneLabels.add((phone.label).obs);
|
||||
}
|
||||
if (phoneControllers.isEmpty) {
|
||||
phoneControllers.add(TextEditingController());
|
||||
phoneLabels.add('Work'.obs);
|
||||
}
|
||||
|
||||
controller.enteredTags.assignAll(
|
||||
widget.existingContact!.tags.map((tag) => tag.name).toList(),
|
||||
);
|
||||
|
||||
ever(controller.isInitialized, (bool ready) {
|
||||
if (ready) {
|
||||
final projectId = widget.existingContact!.projectIds?.firstOrNull;
|
||||
final bucketId = widget.existingContact!.bucketIds.firstOrNull;
|
||||
final categoryName = widget.existingContact!.contactCategory?.name;
|
||||
|
||||
if (categoryName != null) {
|
||||
controller.selectedCategory.value = categoryName;
|
||||
}
|
||||
|
||||
if (projectId != null) {
|
||||
final name = controller.projectsMap.entries
|
||||
.firstWhereOrNull((e) => e.value == projectId)
|
||||
?.key;
|
||||
if (name != null) {
|
||||
controller.selectedProject.value = name;
|
||||
}
|
||||
}
|
||||
|
||||
if (bucketId != null) {
|
||||
final name = controller.bucketsMap.entries
|
||||
.firstWhereOrNull((e) => e.value == bucketId)
|
||||
?.key;
|
||||
if (name != null) {
|
||||
controller.selectedBucket.value = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
emailControllers.add(TextEditingController());
|
||||
emailLabels.add('Office'.obs);
|
||||
phoneControllers.add(TextEditingController());
|
||||
phoneLabels.add('Work'.obs);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
nameController.dispose();
|
||||
orgController.dispose();
|
||||
tagTextController.dispose();
|
||||
addressController.dispose();
|
||||
descriptionController.dispose();
|
||||
emailControllers.forEach((e) => e.dispose());
|
||||
phoneControllers.forEach((p) => p.dispose());
|
||||
Get.delete<AddContactController>();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
InputDecoration _inputDecoration(String hint) => InputDecoration(
|
||||
hintText: hint,
|
||||
hintStyle: MyTextStyle.bodySmall(xMuted: true),
|
||||
@ -116,47 +144,14 @@ class AddContactBottomSheet extends StatelessWidget {
|
||||
isDense: true,
|
||||
);
|
||||
|
||||
Widget _popupSelector({
|
||||
required String hint,
|
||||
required RxString selectedValue,
|
||||
required List<String> options,
|
||||
}) {
|
||||
return Obx(() => GestureDetector(
|
||||
onTap: () async {
|
||||
final selected = await showMenu<String>(
|
||||
context: Navigator.of(Get.context!).overlay!.context,
|
||||
position: const RelativeRect.fromLTRB(100, 300, 100, 0),
|
||||
items: options
|
||||
.map((e) => PopupMenuItem(value: e, child: Text(e)))
|
||||
.toList(),
|
||||
);
|
||||
if (selected != null) selectedValue.value = selected;
|
||||
},
|
||||
child: AbsorbPointer(
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: TextFormField(
|
||||
readOnly: true,
|
||||
initialValue: selectedValue.value,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
decoration: _inputDecoration(hint).copyWith(
|
||||
suffixIcon: const Icon(Icons.expand_more),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildLabeledRow(
|
||||
String label,
|
||||
RxString selectedLabel,
|
||||
List<String> options,
|
||||
String inputLabel,
|
||||
TextEditingController controller,
|
||||
TextInputType inputType, {
|
||||
VoidCallback? onRemove,
|
||||
}) {
|
||||
String label,
|
||||
RxString selectedLabel,
|
||||
List<String> options,
|
||||
String inputLabel,
|
||||
TextEditingController controller,
|
||||
TextInputType inputType,
|
||||
{VoidCallback? onRemove}) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -190,22 +185,19 @@ class AddContactBottomSheet extends StatelessWidget {
|
||||
decoration: _inputDecoration("Enter $inputLabel")
|
||||
.copyWith(counterText: ""),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
if (value == null || value.trim().isEmpty)
|
||||
return "$inputLabel is required";
|
||||
}
|
||||
final trimmed = value.trim();
|
||||
if (inputType == TextInputType.phone) {
|
||||
if (!RegExp(r'^\d{10}$').hasMatch(trimmed)) {
|
||||
return "Enter a valid 10-digit phone number";
|
||||
}
|
||||
if (RegExp(r'^0+$').hasMatch(trimmed)) {
|
||||
return "Phone number cannot be all zeroes";
|
||||
if (!RegExp(r'^[1-9][0-9]{9}$').hasMatch(trimmed)) {
|
||||
return "Enter valid phone number";
|
||||
}
|
||||
}
|
||||
|
||||
if (inputType == TextInputType.emailAddress &&
|
||||
!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
|
||||
!RegExp(r'^[\w.-]+@([\w-]+\.)+[\w-]{2,4}$')
|
||||
.hasMatch(trimmed)) {
|
||||
return "Enter a valid email address";
|
||||
return "Enter valid email";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@ -269,45 +261,54 @@ class AddContactBottomSheet extends StatelessWidget {
|
||||
}),
|
||||
);
|
||||
|
||||
Widget _dropdownField({
|
||||
required String label,
|
||||
Widget _popupSelector({
|
||||
required String hint,
|
||||
required RxString selectedValue,
|
||||
required RxList<String> options,
|
||||
required List<String> options,
|
||||
}) {
|
||||
return Obx(() => SizedBox(
|
||||
height: 48,
|
||||
child: PopupMenuButton<String>(
|
||||
onSelected: (value) => selectedValue.value = value,
|
||||
itemBuilder: (_) => options
|
||||
.map((item) => PopupMenuItem(value: item, child: Text(item)))
|
||||
.toList(),
|
||||
padding: EdgeInsets.zero,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Colors.grey.shade100,
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
selectedValue.value.isEmpty ? label : selectedValue.value,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const Icon(Icons.arrow_drop_down),
|
||||
],
|
||||
),
|
||||
return Obx(() => GestureDetector(
|
||||
onTap: () async {
|
||||
final selected = await showMenu<String>(
|
||||
context: context,
|
||||
position: const RelativeRect.fromLTRB(100, 300, 100, 0),
|
||||
items: options
|
||||
.map((e) => PopupMenuItem(value: e, child: Text(e)))
|
||||
.toList(),
|
||||
);
|
||||
if (selected != null) selectedValue.value = selected;
|
||||
},
|
||||
child: Container(
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
selectedValue.value.isNotEmpty ? selectedValue.value : hint,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
const Icon(Icons.expand_more, size: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _sectionLabel(String title) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.labelLarge(title, fontWeight: 600),
|
||||
MySpacing.height(4),
|
||||
Divider(thickness: 1, color: Colors.grey.shade200),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _tagInputSection() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -327,7 +328,33 @@ class AddContactBottomSheet extends StatelessWidget {
|
||||
),
|
||||
Obx(() => controller.filteredSuggestions.isEmpty
|
||||
? const SizedBox()
|
||||
: _buildSuggestionsList()),
|
||||
: Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: const [
|
||||
BoxShadow(color: Colors.black12, blurRadius: 4)
|
||||
],
|
||||
),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: controller.filteredSuggestions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final suggestion = controller.filteredSuggestions[index];
|
||||
return ListTile(
|
||||
dense: true,
|
||||
title: Text(suggestion),
|
||||
onTap: () {
|
||||
controller.addEnteredTag(suggestion);
|
||||
tagTextController.clear();
|
||||
controller.clearSuggestions();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
)),
|
||||
MySpacing.height(8),
|
||||
Obx(() => Wrap(
|
||||
spacing: 8,
|
||||
@ -342,137 +369,6 @@ class AddContactBottomSheet extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSuggestionsList() => Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 4)],
|
||||
),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: controller.filteredSuggestions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final suggestion = controller.filteredSuggestions[index];
|
||||
return ListTile(
|
||||
dense: true,
|
||||
title: Text(suggestion),
|
||||
onTap: () {
|
||||
controller.addEnteredTag(suggestion);
|
||||
tagTextController.clear();
|
||||
controller.clearSuggestions();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Widget _sectionLabel(String title) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.labelLarge(title, fontWeight: 600),
|
||||
MySpacing.height(4),
|
||||
Divider(thickness: 1, color: Colors.grey.shade200),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 32),
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: MyText.titleMedium(
|
||||
existingContact != null
|
||||
? "Edit Contact"
|
||||
: "Create New Contact",
|
||||
fontWeight: 700,
|
||||
),
|
||||
),
|
||||
MySpacing.height(24),
|
||||
_sectionLabel("Basic Info"),
|
||||
MySpacing.height(16),
|
||||
_buildTextField("Name", nameController),
|
||||
MySpacing.height(16),
|
||||
_buildOrganizationField(),
|
||||
MySpacing.height(24),
|
||||
_sectionLabel("Contact Info"),
|
||||
MySpacing.height(16),
|
||||
Obx(() => _buildEmailList()),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
emailControllers.add(TextEditingController());
|
||||
emailLabels.add('Office'.obs);
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text("Add Email"),
|
||||
),
|
||||
Obx(() => _buildPhoneList()),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
phoneControllers.add(TextEditingController());
|
||||
phoneLabels.add('Work'.obs);
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text("Add Phone"),
|
||||
),
|
||||
MySpacing.height(24),
|
||||
_sectionLabel("Other Details"),
|
||||
MySpacing.height(16),
|
||||
MyText.labelMedium("Category"),
|
||||
MySpacing.height(8),
|
||||
_dropdownField(
|
||||
label: "Select Category",
|
||||
selectedValue: controller.selectedCategory,
|
||||
options: controller.categories,
|
||||
),
|
||||
MySpacing.height(16),
|
||||
MyText.labelMedium("Select Projects"),
|
||||
MySpacing.height(8),
|
||||
_dropdownField(
|
||||
label: "Select Project",
|
||||
selectedValue: controller.selectedProject,
|
||||
options: controller.globalProjects,
|
||||
),
|
||||
MySpacing.height(16),
|
||||
MyText.labelMedium("Select Bucket"),
|
||||
MySpacing.height(8),
|
||||
_dropdownField(
|
||||
label: "Select Bucket",
|
||||
selectedValue: controller.selectedBucket,
|
||||
options: controller.buckets,
|
||||
),
|
||||
MySpacing.height(16),
|
||||
MyText.labelMedium("Tags"),
|
||||
MySpacing.height(8),
|
||||
_tagInputSection(),
|
||||
MySpacing.height(16),
|
||||
_buildTextField("Address", addressController, maxLines: 2),
|
||||
MySpacing.height(16),
|
||||
_buildTextField("Description", descriptionController,
|
||||
maxLines: 2),
|
||||
MySpacing.height(24),
|
||||
_buildActionButtons(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextField(String label, TextEditingController controller,
|
||||
{int maxLines = 1}) {
|
||||
return Column(
|
||||
@ -568,9 +464,9 @@ class AddContactBottomSheet extends StatelessWidget {
|
||||
"phoneNumber": entry.value.text.trim(),
|
||||
})
|
||||
.toList();
|
||||
print("Submitting contact payload , id: ${existingContact?.id}");
|
||||
|
||||
controller.submitContact(
|
||||
id: existingContact?.id,
|
||||
id: widget.existingContact?.id,
|
||||
name: nameController.text.trim(),
|
||||
organization: orgController.text.trim(),
|
||||
emails: emails,
|
||||
@ -594,4 +490,106 @@ class AddContactBottomSheet extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
if (!controller.isInitialized.value) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 32),
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: MyText.titleMedium(
|
||||
widget.existingContact != null
|
||||
? "Edit Contact"
|
||||
: "Create New Contact",
|
||||
fontWeight: 700,
|
||||
),
|
||||
),
|
||||
MySpacing.height(24),
|
||||
_sectionLabel("Basic Info"),
|
||||
MySpacing.height(16),
|
||||
_buildTextField("Name", nameController),
|
||||
MySpacing.height(16),
|
||||
_buildOrganizationField(),
|
||||
MySpacing.height(24),
|
||||
_sectionLabel("Contact Info"),
|
||||
MySpacing.height(16),
|
||||
Obx(() => _buildEmailList()),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
emailControllers.add(TextEditingController());
|
||||
emailLabels.add('Office'.obs);
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text("Add Email"),
|
||||
),
|
||||
Obx(() => _buildPhoneList()),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
phoneControllers.add(TextEditingController());
|
||||
phoneLabels.add('Work'.obs);
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text("Add Phone"),
|
||||
),
|
||||
MySpacing.height(24),
|
||||
_sectionLabel("Other Details"),
|
||||
MySpacing.height(16),
|
||||
MyText.labelMedium("Category"),
|
||||
MySpacing.height(8),
|
||||
_popupSelector(
|
||||
hint: "Select Category",
|
||||
selectedValue: controller.selectedCategory,
|
||||
options: controller.categories,
|
||||
),
|
||||
MySpacing.height(16),
|
||||
MyText.labelMedium("Select Projects"),
|
||||
MySpacing.height(8),
|
||||
_popupSelector(
|
||||
hint: "Select Project",
|
||||
selectedValue: controller.selectedProject,
|
||||
options: controller.globalProjects,
|
||||
),
|
||||
MySpacing.height(16),
|
||||
MyText.labelMedium("Select Bucket"),
|
||||
MySpacing.height(8),
|
||||
_popupSelector(
|
||||
hint: "Select Bucket",
|
||||
selectedValue: controller.selectedBucket,
|
||||
options: controller.buckets,
|
||||
),
|
||||
MySpacing.height(16),
|
||||
MyText.labelMedium("Tags"),
|
||||
MySpacing.height(8),
|
||||
_tagInputSection(),
|
||||
MySpacing.height(16),
|
||||
_buildTextField("Address", addressController, maxLines: 2),
|
||||
MySpacing.height(16),
|
||||
_buildTextField("Description", descriptionController,
|
||||
maxLines: 2),
|
||||
MySpacing.height(24),
|
||||
_buildActionButtons(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user