made chnages for all employee

This commit is contained in:
Vaibhav Surve 2025-08-07 17:14:04 +05:30
parent 10e4a6e514
commit 7175ade940
6 changed files with 118 additions and 176 deletions

View File

@ -22,42 +22,6 @@ class AddEmployeeController extends MyController {
Gender? selectedGender; Gender? selectedGender;
List<Map<String, dynamic>> roles = []; List<Map<String, dynamic>> roles = [];
String? selectedRoleId; String? selectedRoleId;
final List<Map<String, String>> 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<String, int> minDigitsPerCountry = {
"+91": 10,
"+1": 10,
"+971": 9,
"+44": 10,
"+81": 10,
"+61": 9,
"+49": 10,
"+33": 9,
"+86": 11,
};
final Map<String, int> maxDigitsPerCountry = {
"+91": 10,
"+1": 10,
"+971": 9,
"+44": 11,
"+81": 10,
"+61": 9,
"+49": 11,
"+33": 9,
"+86": 11,
};
String selectedCountryCode = "+91"; String selectedCountryCode = "+91";
bool showOnline = true; bool showOnline = true;
final List<String> categories = []; final List<String> categories = [];

View File

@ -1180,8 +1180,9 @@ class ApiService {
static Future<List<dynamic>?> getAllEmployeesByProject( static Future<List<dynamic>?> getAllEmployeesByProject(
String projectId) async { String projectId) async {
if (projectId.isEmpty) throw ArgumentError('projectId must not be empty'); 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( return _getRequest(endpoint).then(
(res) => res != null (res) => res != null
? _parseResponse(res, label: 'Employees by Project') ? _parseResponse(res, label: 'Employees by Project')

View File

@ -147,7 +147,8 @@ class AuthService {
required String mpin, required String mpin,
}) async { }) async {
final token = await LocalStorage.getJwtToken(); final token = await LocalStorage.getJwtToken();
logSafe("Generating MPIN for employeeId: $employeeId");
logSafe("MPIN: $mpin");
try { try {
logSafe("Generating MPIN..."); logSafe("Generating MPIN...");
final response = await http.post( final response = await http.post(

View File

@ -147,88 +147,52 @@ class _AddEmployeeBottomSheetState extends State<AddEmployeeBottomSheet>
// Phone input with country code selector // Phone input with country code selector
Widget _buildPhoneInput(BuildContext context) { Widget _buildPhoneInput(BuildContext context) {
return Row( return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( MyText.labelMedium("Phone Number"),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), MySpacing.height(8),
decoration: BoxDecoration( Row(
border: Border.all(color: Colors.grey.shade300), children: [
borderRadius: BorderRadius.circular(12), Container(
color: Colors.grey.shade100, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
), decoration: BoxDecoration(
child: PopupMenuButton<Map<String, String>>( border: Border.all(color: Colors.grey.shade300),
onSelected: (country) { borderRadius: BorderRadius.circular(12),
_controller.selectedCountryCode = country['code']!; color: Colors.grey.shade100,
_controller.update(); ),
}, child: Text("+91"),
itemBuilder: (context) => [ ),
PopupMenuItem( MySpacing.width(12),
enabled: false, Expanded(
padding: EdgeInsets.zero, child: TextFormField(
child: SizedBox( controller:
height: 200, _controller.basicValidator.getController('phone_number'),
width: 100, validator: (value) {
child: ListView( if (value == null || value.trim().isEmpty) {
children: _controller.countries.map((country) { return "Phone number is required";
return ListTile( }
dense: true,
title: Text("${country['name']} (${country['code']})"), if (!RegExp(r'^\d{10}$').hasMatch(value.trim())) {
onTap: () => Navigator.pop(context, country), return "Enter a valid 10-digit number";
); }
}).toList(),
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),
),
),
),
), ),
], ],
); );

View File

@ -83,21 +83,27 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
void _filterEmployees(String query) { void _filterEmployees(String query) {
final employees = _employeeController.employees; final employees = _employeeController.employees;
List<EmployeeModel> filtered;
if (query.isEmpty) { if (query.isEmpty) {
_filteredEmployees.assignAll(employees); filtered = List<EmployeeModel>.from(employees);
return; } 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(); filtered
_filteredEmployees.assignAll( .sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
employees.where(
(e) => _filteredEmployees.assignAll(filtered);
e.name.toLowerCase().contains(q) ||
e.email.toLowerCase().contains(q) ||
e.phoneNumber.toLowerCase().contains(q) ||
e.jobRole.toLowerCase().contains(q),
),
);
} }
Future<void> _onAddNewEmployee() async { Future<void> _onAddNewEmployee() async {
@ -225,8 +231,7 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
Widget _buildFloatingActionButton() { Widget _buildFloatingActionButton() {
if (!_permissionController.hasPermission(Permissions.manageEmployees)) { if (!_permissionController.hasPermission(Permissions.manageEmployees)) {
return const SizedBox return const SizedBox.shrink();
.shrink();
} }
return InkWell( return InkWell(

View File

@ -129,55 +129,62 @@ class ExpenseFilterBottomSheet extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Obx(() { Obx(() {
return SegmentedButton<String>( return SizedBox(
segments: expenseController.dateTypes width: double.infinity, // Make it full width
.map( child: SegmentedButton<String>(
(type) => ButtonSegment( segments: expenseController.dateTypes
value: type, .map(
label: MyText( (type) => ButtonSegment(
type, value: type,
style: MyTextStyle.bodySmall( label: Center(
fontWeight: 600, // Center label text
fontSize: 13, child: MyText(
height: 1.2, type,
style: MyTextStyle.bodySmall(
fontWeight: 600,
fontSize: 13,
height: 1.2,
),
),
), ),
), ),
), )
) .toList(),
.toList(), selected: {expenseController.selectedDateType.value},
selected: {expenseController.selectedDateType.value}, onSelectionChanged: (newSelection) {
onSelectionChanged: (newSelection) { if (newSelection.isNotEmpty) {
if (newSelection.isNotEmpty) { expenseController.selectedDateType.value =
expenseController.selectedDateType.value = newSelection.first; newSelection.first;
} }
}, },
style: ButtonStyle( style: ButtonStyle(
visualDensity: visualDensity:
const VisualDensity(horizontal: -2, vertical: -2), const VisualDensity(horizontal: -2, vertical: -2),
padding: MaterialStateProperty.all( padding: MaterialStateProperty.all(
const EdgeInsets.symmetric(horizontal: 8, vertical: 6), 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),
), ),
), backgroundColor: MaterialStateProperty.resolveWith(
side: MaterialStateProperty.resolveWith( (states) => states.contains(MaterialState.selected)
(states) => BorderSide( ? Colors.indigo.shade100
color: states.contains(MaterialState.selected) : Colors.grey.shade100,
),
foregroundColor: MaterialStateProperty.resolveWith(
(states) => states.contains(MaterialState.selected)
? Colors.indigo ? Colors.indigo
: Colors.grey.shade300, : Colors.black87,
width: 1, ),
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,
),
), ),
), ),
), ),