import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:marco/controller/project/create_project_controller.dart'; import 'package:marco/helpers/utils/base_bottom_sheet.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/helpers/widgets/my_snackbar.dart'; class CreateProjectBottomSheet extends StatefulWidget { const CreateProjectBottomSheet({Key? key}) : super(key: key); @override State createState() => _CreateProjectBottomSheetState(); } class _CreateProjectBottomSheetState extends State { final _formKey = GlobalKey(); final CreateProjectController controller = Get.put(CreateProjectController()); DateTime? _startDate; DateTime? _endDate; Future _pickDate({required bool isStart}) async { final picked = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2100), ); if (picked != null) { setState(() { if (isStart) { _startDate = picked; controller.startDateCtrl.text = DateFormat('yyyy-MM-dd').format(picked); } else { _endDate = picked; controller.endDateCtrl.text = DateFormat('yyyy-MM-dd').format(picked); } }); } } Future _handleSubmit() async { if (!(_formKey.currentState?.validate() ?? false)) return; if (_startDate == null || _endDate == null) { showAppSnackbar( title: "Error", message: "Please select both start and end dates", type: SnackbarType.error, ); return; } if (controller.selectedStatus == null) { showAppSnackbar( title: "Error", message: "Please select project status", type: SnackbarType.error, ); return; } // Call API final success = await controller.createProject( name: controller.nameCtrl.text.trim(), shortName: controller.shortNameCtrl.text.trim(), projectAddress: controller.addressCtrl.text.trim(), contactPerson: controller.contactCtrl.text.trim(), startDate: _startDate!, endDate: _endDate!, projectStatusId: controller.selectedStatus!.id, ); if (success) Navigator.pop(context); } @override Widget build(BuildContext context) { return BaseBottomSheet( title: "Create Project", onCancel: () => Navigator.pop(context), onSubmit: _handleSubmit, child: Form( key: _formKey, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MySpacing.height(16), /// Project Name LabeledInput( label: "Project Name", hint: "Enter project name", controller: controller.nameCtrl, validator: (value) => value == null || value.trim().isEmpty ? "Required" : null, isRequired: true, ), MySpacing.height(16), /// Short Name LabeledInput( label: "Short Name", hint: "Enter short name", controller: controller.shortNameCtrl, validator: (value) => value == null || value.trim().isEmpty ? "Required" : null, isRequired: true, ), MySpacing.height(16), /// Project Address LabeledInput( label: "Project Address", hint: "Enter project address", controller: controller.addressCtrl, validator: (value) => value == null || value.trim().isEmpty ? "Required" : null, isRequired: true, ), MySpacing.height(16), /// Contact Person LabeledInput( label: "Contact Person", hint: "Enter contact person", controller: controller.contactCtrl, validator: (value) => value == null || value.trim().isEmpty ? "Required" : null, isRequired: true, ), MySpacing.height(16), /// Start Date GestureDetector( onTap: () => _pickDate(isStart: true), child: AbsorbPointer( child: LabeledInput( label: "Start Date", hint: "Select start date", controller: controller.startDateCtrl, validator: (value) => _startDate == null ? "Required" : null, isRequired: true, ), ), ), MySpacing.height(16), /// End Date GestureDetector( onTap: () => _pickDate(isStart: false), child: AbsorbPointer( child: LabeledInput( label: "End Date", hint: "Select end date", controller: controller.endDateCtrl, validator: (value) => _endDate == null ? "Required" : null, isRequired: true, ), ), ), MySpacing.height(16), /// Project Status using PopupMenuButton Obx(() { if (controller.statusList.isEmpty) { return const Center(child: CircularProgressIndicator()); } return LabeledDropdownPopup( label: "Project Status", hint: "Select status", value: controller.selectedStatus?.name, items: controller.statusList.map((e) => e.name).toList(), onChanged: (selected) { final status = controller.statusList .firstWhere((s) => s.name == selected); setState(() => controller.selectedStatus = status); }, isRequired: true, ); }), MySpacing.height(16), ], ), ), ), ); } } /// ----------------- LabeledInput ----------------- class LabeledInput extends StatelessWidget { final String label; final String hint; final TextEditingController controller; final String? Function(String?) validator; final bool isRequired; const LabeledInput({ Key? key, required this.label, required this.hint, required this.controller, required this.validator, this.isRequired = false, }) : super(key: key); @override Widget build(BuildContext context) => Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ MyText.labelMedium(label), if (isRequired) const Text( " *", style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold), ), ], ), MySpacing.height(8), TextFormField( controller: controller, validator: validator, decoration: InputDecoration( hintText: hint, hintStyle: MyTextStyle.bodySmall(xMuted: true), filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade300), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade300), ), focusedBorder: const OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(12)), borderSide: BorderSide(color: Colors.blueAccent, width: 1.5), ), contentPadding: EdgeInsets.all(16), ), ), ], ); } /// ----------------- LabeledDropdownPopup ----------------- class LabeledDropdownPopup extends StatelessWidget { final String label; final String hint; final String? value; final List items; final ValueChanged onChanged; final bool isRequired; LabeledDropdownPopup({ Key? key, required this.label, required this.hint, required this.value, required this.items, required this.onChanged, this.isRequired = false, }) : super(key: key); final GlobalKey _fieldKey = GlobalKey(); @override Widget build(BuildContext context) => Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ MyText.labelMedium(label), if (isRequired) const Text( " *", style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold), ), ], ), MySpacing.height(8), GestureDetector( key: _fieldKey, onTap: () async { // Get the position of the widget final RenderBox box = _fieldKey.currentContext!.findRenderObject() as RenderBox; final Offset offset = box.localToGlobal(Offset.zero); final RelativeRect position = RelativeRect.fromLTRB( offset.dx, offset.dy + box.size.height, offset.dx + box.size.width, offset.dy, ); final selected = await showMenu( context: context, position: position, items: items .map((item) => PopupMenuItem( value: item, child: Text(item), )) .toList(), ); if (selected != null) onChanged(selected); }, child: AbsorbPointer( child: TextFormField( readOnly: true, controller: TextEditingController(text: value ?? ""), validator: (val) => isRequired && (val == null || val.isEmpty) ? "Required" : null, decoration: InputDecoration( hintText: hint, hintStyle: MyTextStyle.bodySmall(xMuted: true), filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade300), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade300), ), focusedBorder: const OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(12)), borderSide: BorderSide(color: Colors.blueAccent, width: 1.5), ), contentPadding: const EdgeInsets.all(16), suffixIcon: const Icon(Icons.expand_more), ), ), ), ), ], ); }