made chnages for all employee
This commit is contained in:
parent
10e4a6e514
commit
7175ade940
@ -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 = [];
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user