diff --git a/lib/controller/dashboard/add_employee_controller.dart b/lib/controller/dashboard/add_employee_controller.dart index f016ab3..68e3b58 100644 --- a/lib/controller/dashboard/add_employee_controller.dart +++ b/lib/controller/dashboard/add_employee_controller.dart @@ -22,42 +22,6 @@ class AddEmployeeController extends MyController { Gender? selectedGender; List> roles = []; String? selectedRoleId; - final List> countries = [ - {"code": "+91", "name": "India"}, - {"code": "+1", "name": "USA"}, - {"code": "+971", "name": "UAE"}, - {"code": "+44", "name": "UK"}, - {"code": "+81", "name": "Japan"}, - {"code": "+61", "name": "Australia"}, - {"code": "+49", "name": "Germany"}, - {"code": "+33", "name": "France"}, - {"code": "+86", "name": "China"}, - ]; - - final Map minDigitsPerCountry = { - "+91": 10, - "+1": 10, - "+971": 9, - "+44": 10, - "+81": 10, - "+61": 9, - "+49": 10, - "+33": 9, - "+86": 11, - }; - - final Map maxDigitsPerCountry = { - "+91": 10, - "+1": 10, - "+971": 9, - "+44": 11, - "+81": 10, - "+61": 9, - "+49": 11, - "+33": 9, - "+86": 11, - }; - String selectedCountryCode = "+91"; bool showOnline = true; final List categories = []; @@ -156,7 +120,7 @@ class AddEmployeeController extends MyController { message: "Employee created successfully!", type: SnackbarType.success, ); - return response; + return response; } else { logSafe("Failed to create employee (response false)", level: LogLevel.error); diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index 0062b1f..8428b70 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -1180,8 +1180,9 @@ class ApiService { static Future?> getAllEmployeesByProject( String projectId) async { if (projectId.isEmpty) throw ArgumentError('projectId must not be empty'); - final endpoint = - "${ApiEndpoints.getAllEmployeesByProject}?projectId=$projectId"; + + final endpoint = "${ApiEndpoints.getAllEmployeesByProject}/$projectId"; + return _getRequest(endpoint).then( (res) => res != null ? _parseResponse(res, label: 'Employees by Project') diff --git a/lib/helpers/services/auth_service.dart b/lib/helpers/services/auth_service.dart index 5c0bc0c..541771c 100644 --- a/lib/helpers/services/auth_service.dart +++ b/lib/helpers/services/auth_service.dart @@ -147,7 +147,8 @@ class AuthService { required String mpin, }) async { final token = await LocalStorage.getJwtToken(); - +logSafe("Generating MPIN for employeeId: $employeeId"); +logSafe("MPIN: $mpin"); try { logSafe("Generating MPIN..."); final response = await http.post( diff --git a/lib/model/employees/add_employee_bottom_sheet.dart b/lib/model/employees/add_employee_bottom_sheet.dart index 91fee4e..e4fd3b3 100644 --- a/lib/model/employees/add_employee_bottom_sheet.dart +++ b/lib/model/employees/add_employee_bottom_sheet.dart @@ -147,88 +147,52 @@ class _AddEmployeeBottomSheetState extends State // Phone input with country code selector Widget _buildPhoneInput(BuildContext context) { - return Row( + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(12), - color: Colors.grey.shade100, - ), - child: PopupMenuButton>( - onSelected: (country) { - _controller.selectedCountryCode = country['code']!; - _controller.update(); - }, - itemBuilder: (context) => [ - PopupMenuItem( - enabled: false, - padding: EdgeInsets.zero, - child: SizedBox( - height: 200, - width: 100, - child: ListView( - children: _controller.countries.map((country) { - return ListTile( - dense: true, - title: Text("${country['name']} (${country['code']})"), - onTap: () => Navigator.pop(context, country), - ); - }).toList(), + MyText.labelMedium("Phone Number"), + MySpacing.height(8), + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + color: Colors.grey.shade100, + ), + child: Text("+91"), + ), + MySpacing.width(12), + Expanded( + child: TextFormField( + controller: + _controller.basicValidator.getController('phone_number'), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return "Phone number is required"; + } + + if (!RegExp(r'^\d{10}$').hasMatch(value.trim())) { + return "Enter a valid 10-digit number"; + } + + return null; + }, + keyboardType: TextInputType.phone, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(10), + ], + decoration: _inputDecoration("e.g., 9876543210").copyWith( + suffixIcon: IconButton( + icon: const Icon(Icons.contacts), + onPressed: () => _controller.pickContact(context), ), ), ), - ], - child: Row( - children: [ - Text(_controller.selectedCountryCode), - const Icon(Icons.arrow_drop_down), - ], ), - ), - ), - MySpacing.width(12), - Expanded( - child: TextFormField( - controller: - _controller.basicValidator.getController('phone_number'), - validator: (value) { - if (value == null || value.trim().isEmpty) { - return "Phone number is required"; - } - - final digitsOnly = value.trim(); - final minLength = _controller - .minDigitsPerCountry[_controller.selectedCountryCode] ?? - 7; - final maxLength = _controller - .maxDigitsPerCountry[_controller.selectedCountryCode] ?? - 15; - - if (!RegExp(r'^[0-9]+$').hasMatch(digitsOnly)) { - return "Only digits allowed"; - } - - if (digitsOnly.length < minLength || - digitsOnly.length > maxLength) { - return "Between $minLength–$maxLength digits"; - } - - return null; - }, - keyboardType: TextInputType.phone, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(15), - ], - decoration: _inputDecoration("e.g., 9876543210").copyWith( - suffixIcon: IconButton( - icon: const Icon(Icons.contacts), - onPressed: () => _controller.pickContact(context), - ), - ), - ), + ], ), ], ); diff --git a/lib/view/employees/employees_screen.dart b/lib/view/employees/employees_screen.dart index e7c3a10..a1ef672 100644 --- a/lib/view/employees/employees_screen.dart +++ b/lib/view/employees/employees_screen.dart @@ -83,21 +83,27 @@ class _EmployeesScreenState extends State with UIMixin { void _filterEmployees(String query) { final employees = _employeeController.employees; + List filtered; + if (query.isEmpty) { - _filteredEmployees.assignAll(employees); - return; + filtered = List.from(employees); + } else { + final q = query.toLowerCase(); + filtered = employees + .where( + (e) => + e.name.toLowerCase().contains(q) || + e.email.toLowerCase().contains(q) || + e.phoneNumber.toLowerCase().contains(q) || + e.jobRole.toLowerCase().contains(q), + ) + .toList(); } - final q = query.toLowerCase(); - _filteredEmployees.assignAll( - employees.where( - (e) => - e.name.toLowerCase().contains(q) || - e.email.toLowerCase().contains(q) || - e.phoneNumber.toLowerCase().contains(q) || - e.jobRole.toLowerCase().contains(q), - ), - ); + filtered + .sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); + + _filteredEmployees.assignAll(filtered); } Future _onAddNewEmployee() async { @@ -225,8 +231,7 @@ class _EmployeesScreenState extends State with UIMixin { Widget _buildFloatingActionButton() { if (!_permissionController.hasPermission(Permissions.manageEmployees)) { - return const SizedBox - .shrink(); + return const SizedBox.shrink(); } return InkWell( diff --git a/lib/view/expense/expense_filter_bottom_sheet.dart b/lib/view/expense/expense_filter_bottom_sheet.dart index 1162ba5..ea361a2 100644 --- a/lib/view/expense/expense_filter_bottom_sheet.dart +++ b/lib/view/expense/expense_filter_bottom_sheet.dart @@ -129,55 +129,62 @@ class ExpenseFilterBottomSheet extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Obx(() { - return SegmentedButton( - segments: expenseController.dateTypes - .map( - (type) => ButtonSegment( - value: type, - label: MyText( - type, - style: MyTextStyle.bodySmall( - fontWeight: 600, - fontSize: 13, - height: 1.2, + return SizedBox( + width: double.infinity, // Make it full width + child: SegmentedButton( + segments: expenseController.dateTypes + .map( + (type) => ButtonSegment( + value: type, + label: Center( + // Center label text + child: MyText( + type, + style: MyTextStyle.bodySmall( + fontWeight: 600, + fontSize: 13, + height: 1.2, + ), + ), ), ), - ), - ) - .toList(), - selected: {expenseController.selectedDateType.value}, - onSelectionChanged: (newSelection) { - if (newSelection.isNotEmpty) { - expenseController.selectedDateType.value = newSelection.first; - } - }, - style: ButtonStyle( - visualDensity: - const VisualDensity(horizontal: -2, vertical: -2), - padding: MaterialStateProperty.all( - const EdgeInsets.symmetric(horizontal: 8, vertical: 6), - ), - backgroundColor: MaterialStateProperty.resolveWith( - (states) => states.contains(MaterialState.selected) - ? Colors.indigo.shade100 - : Colors.grey.shade100, - ), - foregroundColor: MaterialStateProperty.resolveWith( - (states) => states.contains(MaterialState.selected) - ? Colors.indigo - : Colors.black87, - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + ) + .toList(), + selected: {expenseController.selectedDateType.value}, + onSelectionChanged: (newSelection) { + if (newSelection.isNotEmpty) { + expenseController.selectedDateType.value = + newSelection.first; + } + }, + style: ButtonStyle( + visualDensity: + const VisualDensity(horizontal: -2, vertical: -2), + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(horizontal: 8, vertical: 6), ), - ), - side: MaterialStateProperty.resolveWith( - (states) => BorderSide( - color: states.contains(MaterialState.selected) + backgroundColor: MaterialStateProperty.resolveWith( + (states) => states.contains(MaterialState.selected) + ? Colors.indigo.shade100 + : Colors.grey.shade100, + ), + foregroundColor: MaterialStateProperty.resolveWith( + (states) => states.contains(MaterialState.selected) ? Colors.indigo - : Colors.grey.shade300, - width: 1, + : Colors.black87, + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + side: MaterialStateProperty.resolveWith( + (states) => BorderSide( + color: states.contains(MaterialState.selected) + ? Colors.indigo + : Colors.grey.shade300, + width: 1, + ), ), ), ),