nhance_partner/lib/presentation/screens/UserManagement/SalesExecutive/salesExecutive.dart
2026-01-08 16:01:06 +05:30

938 lines
31 KiB
Dart

import 'dart:convert';
// import 'dart:io' as html;
import 'dart:typed_data'; // Import for Uint8List
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:nhance_partner/core/routing/routes.dart';
import 'package:nhance_partner/data/utils/toastNotification.dart';
import 'package:nhance_partner/presentation/themes/indicators/text_field_theme.dart';
import '../../../../core/config/env.dart';
import '../../../../core/services/api_service.dart';
import '../../../../data/services/auth_service.dart';
import '../../../../data/utils/validators.dart';
import '../../../layouts/main_layout.dart';
import '../../../layouts/responsive_layout.dart';
import '../../../providers/manager_provider.dart';
import 'package:universal_html/html.dart' as html;
import '../../../themes/indicators/input_field_decoration.dart';
class SalesExecutive extends ConsumerStatefulWidget {
final String? id;
final void Function(String value) onSubmit;
const SalesExecutive({super.key, this.id, required this.onSubmit});
@override
ConsumerState<SalesExecutive> createState() => SalesExecutiveState();
}
class SalesExecutiveState extends ConsumerState<SalesExecutive> {
final _formKey = GlobalKey<FormState>();
late ApiService apiService;
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKey =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>>
dropDownKeyHandler = GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
List<String> tabHeader = ['name', 'email', 'mobile', 'code', 'address'];
late String isActive = "1";
html.File? passportFile;
String? selectedFileNames;
String? passportFileUrlFromApi;
String? selectedId;
String? _token;
bool isLoading = false;
bool showHandler = false;
String? selectedRole;
List<Map<String, dynamic>> filteredRolesData = [];
List<Map<String, dynamic>> getRolesData = [];
List<dynamic>? selectedHandlerIds = [];
String? selectedHandler;
List<Map<String, dynamic>> filteredHandlersData = [];
List<Map<String, dynamic>> getHandlersData = [];
Map<String, TextEditingController> controllers = {};
dynamic userId;
dynamic managerId;
Map<String, dynamic> dataDetails() {
final data = {
"name": controllers["name"]?.text,
"email": controllers["email"]?.text,
"mobile": controllers["mobile"]?.text,
"address": controllers["address"]?.text,
// "emp_id": controllers["code"]?.text,
"is_active": isActive,
// "handler_id": selectedHandler,
"manager_id": managerId,
};
return data;
}
@override
void initState() {
super.initState();
apiService = ApiService();
for (String field in tabHeader) {
controllers[field] = TextEditingController();
}
_initializeToken();
updateData();
getRole();
Future.microtask(() {
managerId = ref.watch(managerIdProvider);
userId = ref.watch(userIdProvider);
if (managerId != null) {
print('hansles');
getHandlers(managerId);
}
});
}
Future<void> _initializeToken() async {
_token = await AuthService.getToken();
print("APISERTOKEN - $_token");
}
void updateData() async {
if (widget.id != null && widget.id != 'create') {
dynamic response = await apiService.findSingleSalesExecutiveData(
widget.id!,
);
final data = response['data'];
print("updateData - ${response['data']}");
if (data == null) return;
setState(() {
selectedId = widget.id;
controllers['name']?.text = data['name'] ?? '';
controllers['email']?.text = data['email'] ?? '';
controllers['mobile']?.text = data['mobile'] ?? '';
controllers['address']?.text = data['address'] ?? '';
// controllers['code']?.text = data['emp_id'] ?? '';
isActive = data["is_active"];
});
}
}
Future<void> handleSave() async {
if (!_formKey.currentState!.validate()) return;
final email = controllers['email']!.text.trim();
final phone = controllers['mobile']!.text.trim();
// 🔴 Conditional validation: at least one required
if (email.isEmpty && phone.isEmpty) {
ToastHelper.showSuccessToast(
context,
'Please enter either Email or Phone Number',
);
return;
}
setState(() {
if (_formKey.currentState!.validate()) {
dataDetails();
final dataSet = dataDetails();
print("dataSetAgent - $dataSet");
createUserData(dataSet);
} else {
// isDisable = false;
}
});
}
Future<void> createUserData(data) async {
final bool isUpdating = widget.id != null && widget.id != 'create';
final String? id = isUpdating ? widget.id : null;
final String apiUrldata;
apiUrldata = isUpdating
? '${Env.apiUrl}salesExecutive/updateExecutive'
: '${Env.apiUrl}salesExecutive/createExecutive';
// final token = await getToken(); // Fetch token
if (_token == null) {
throw Exception('Token not found. Please log in.');
}
if (isUpdating) {
data['id'] = id; // Add plan_id for update
data['updated_by'] = userId;
} else {
data['created_by'] = userId;
}
print("data------- $data}");
try {
final response = await http.post(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $_token',
'Content-Type': 'application/json',
'app-signature': Env.App_Signature,
},
body: jsonEncode(data), // Convert map to JSON
);
if (response.statusCode == 200) {
print("SalesExecutive submitted successfully!");
print("Response: ${response.body}");
isUpdating
? ToastHelper.showSuccessToast(
context,
'SalesExecutive Updated Successfully',
)
: ToastHelper.showSuccessToast(
context,
'SalesExecutive Created Successfully',
);
Navigator.of(context).pop();
widget.onSubmit("success");
// context.go(AppRoutes.salesExecutiveLst);
} else if (response.statusCode == 403) {
await apiService.clearLocalStorageAndRedirect();
} else {
final responseBody = jsonDecode(response.body);
dynamic msg = responseBody['data'];
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("SalesExecutive User Creation Failed"),
content: Text(msg),
actions: [
TextButton(
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
} catch (e) {
print(" Error submitting SalesExecutive: $e");
}
}
Future<void> getRole() async {
print('Handlers called');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchMasterDropDown('staffRole');
if (response['status'] == 200) {
print('getRole - ${response['data']}');
setState(() {
getRolesData = List<Map<String, dynamic>>.from(response['data']);
print('API Data - $getRolesData');
filteredRolesData = List.from(getRolesData);
print('originalData - $filteredRolesData');
});
} else {
getRolesData = [];
filteredRolesData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> getHandlers(managerId) async {
print('Handlers called');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchHandlerNameDropDown(managerId);
if (response['code'] == 200 || response['status'] == 'success') {
print('getHandlers -');
print('getHandlers - ${response['data']}');
setState(() {
getHandlersData = List<Map<String, dynamic>>.from(response['data']);
print('API HandlersData - $getHandlersData');
filteredHandlersData = List.from(getHandlersData);
print('HandlersoriginalData - $filteredHandlersData');
});
} else {
getHandlersData = [];
filteredHandlersData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
@override
void dispose() {
for (var controller in controllers.values) {
controller.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return SelectionArea(
child: AlertDialog(
backgroundColor: Colors.white,
content: Container(
// color: Colors.yellow.shade50,
width: MediaQuery.of(context).size.width * 0.42,
height: MediaQuery.of(context).size.height * 0.55,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
// height: 30,
// color: Colors.red.shade50,
width: MediaQuery.of(context).size.width,
child: GestureDetector(
onTap: () {
context.go(AppRoutes.staffLst);
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// Tooltip(
// message: 'Back',
// child: IconButton(
// icon: const Icon(
// Icons.arrow_left_sharp,
// size: 25,
// color: Color(0xFF425B5B),
// ),
// onPressed: () {
// context.go(AppRoutes.salesExecutiveLst);
// },
// splashRadius: 28,
// hoverColor: Colors.black12,
// padding: const EdgeInsets.all(8),
// constraints: const BoxConstraints(),
// ),
// ),
const SizedBox(width: 5), // spacing between icon and text
Text(
"Sales Executive",
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
Spacer(),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
padding: const EdgeInsets.all(5.0),
decoration: BoxDecoration(
color: const Color(0xFFF1F1F1),
borderRadius: BorderRadius.circular(5.0),
),
child: Tooltip(
message: 'Close',
child: const Icon(Icons.close, size: 18),
),
),
),
],
),
),
),
// SizedBox(height: 10),
Expanded(
child: Container(
// color: Colors.green,
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.all(8.0),
padding: EdgeInsets.symmetric(horizontal: 14.0, vertical: 20.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: Colors.white,
// color: Color(0xFFEDF6F5),
),
child: Column(
children: [
Row(children: [Expanded(child: buildFormFields())]),
Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () {
handleSave();
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 45.0,
vertical: 8,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: Color(0xFF2E7D6E),
// color: Color(0xFF425B5B),
),
child: Text(
'Save',
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 12,
),
),
),
),
],
),
// SizedBox(height: 30),
],
),
),
),
SizedBox(height: 10),
],
),
),
),);
}
Widget buildFormFields() {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(children: [buildName(), SizedBox(width: 25), buildEmail()]),
SizedBox(height: 20),
Row(children: [buildPhNumber(), SizedBox(width: 25), buildAddress()]),
],
),
);
}
Widget buildName() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Name *", style: _textStyle),
SizedBox(height: 10),
ThemedFormField(
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
controller: controllers['name']!,
validator: (value) => Validators.requiredField(value, "name"),
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildEmail() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Email *", style: _textStyle),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['email']!,
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
// validator: (value) => Validators.email(value, "email"),
validator: (value) {
final phone = controllers['mobile']!.text.trim();
if (value!.isEmpty && phone.isNotEmpty) {
return null; // phone is given → email not required
}
return Validators.email(value, "email");
},
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_@.]')),
],
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildPhNumber() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Phone Number *", style: _textStyle),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['mobile']!,
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
// validator: (value) => Validators.phone(value, "phNumber"),
validator: (value) {
final email = controllers['email']!.text.trim();
if (value!.isEmpty && email.isNotEmpty) {
return null; // email is given → phone not required
}
return Validators.phone(value, "phNumber");
},
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
],
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildAddress() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Address", style: _textStyle),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['address']!,
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[ a-zA-Z0-9_#/.]')),
],
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildId() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("SalesExecutive Id ", style: _textStyle),
SizedBox(width: 10),
ThemedFormField(
controller: controllers['code']!,
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
// validator: (value) => Validators.requiredField(value, "id"),
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
// Widget buildAddress() {
// return Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text("Role*", style: _textStyle),
// SizedBox(width: 10),
// ThemedFormField(
// controller: controllers['address']!,
//
// txtwidth: MediaQuery.of(context).size.width *0.18,
// ),
// ],
// );
// }
Widget buildRole(BuildContext context) {
Map<String, dynamic>? selectedroleVal = filteredRolesData.firstWhere(
(item) => item['id'].toString() == selectedRole,
orElse: () => {},
);
if (selectedroleVal.isNotEmpty &&
selectedroleVal['role'] != null &&
selectedroleVal['role'] == 'SalesExecutive') {
showHandler = true;
} else {
selectedHandlerIds = [];
showHandler = false;
}
final isReadOnly = widget.id != null;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Role *", style: _textStyle),
SizedBox(width: 10),
Container(
decoration: BoxDecoration(
// color: Colors.white,
borderRadius: BorderRadius.circular(10.0),
),
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.18,
// height: 40,
child: AbsorbPointer(
absorbing: isReadOnly,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKey,
selectedItem: selectedroleVal.isNotEmpty ? selectedroleVal : null,
items: (filter, infiniteScrollProps) {
return filteredRolesData;
},
itemAsString: (val) => val['role'].toString(), // what to show
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // ✅ compare by id
validator: (val) {
if (val == null) {
return "Required"; // ✅ error message
}
return null;
},
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Role",
).copyWith(
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0xFFE2E8F0),
width: 1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0xFFE2E8F0),
width: 1,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
),
),
),
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
menuProps: MenuProps(
backgroundColor:
Colors.white, // 👈 sets dropdown background to white
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
autofocus: true,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(3),
filled: true,
fillColor: Colors.white,
hintText: "Search Role...",
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFE2E8F0),
// color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFE2E8F0),
// color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
enabled: widget.id == null,
),
),
// constraints: BoxConstraints(),
),
onChanged: widget.id != null
? null
: (val) {
if (val != null) {
print("Selected Role : ${val['role']}");
print("Id: ${val['id']}");
selectedRole = val['id'];
if (val['role'] == 'SalesExecutive') {
setState(() {
showHandler = true;
});
} else {
setState(() {
showHandler = false;
});
}
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
},
),
),
),
],
);
}
Widget buildHandler(BuildContext context) {
Map<String, dynamic>? selectedHandlered = filteredHandlersData.firstWhere(
(item) => item['id'].toString() == selectedHandler,
orElse: () => {},
);
// final isReadOnly = widget.id != null;
final isReadOnly = false;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Handler Name *", style: _textStyle),
SizedBox(width: 10),
Container(
decoration: BoxDecoration(
// color: Colors.white,
borderRadius: BorderRadius.circular(10.0),
),
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.18,
// height: 40,
child: AbsorbPointer(
absorbing: isReadOnly,
// child: DropdownSearch<Map<String, dynamic>>(
child: DropdownSearch<Map<String, dynamic>>.multiSelection(
key: dropDownKeyHandler,
// selectedItem: selectedHandlered.isNotEmpty
// ? selectedHandlered
// : null,
selectedItems: filteredHandlersData
.where(
(item) => (selectedHandlerIds ?? []).contains(item['id']),
)
.toList(),
items: (filter, infiniteScrollProps) {
return filteredHandlersData;
},
itemAsString: (val) => val['name'].toString(), // what to show
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // ✅ compare by id
// validator: (val) {
// if (val == null) {
// return "Required"; // ✅ error message
// }
// return null;
// },
validator: (val) {
if (val == null || val.isEmpty) {
return "Required";
}
return null;
},
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Handler",
).copyWith(
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFE2E8F0),
// color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFE2E8F0),
// color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
),
),
),
popupProps: PopupPropsMultiSelection.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
showSearchBox: true,
menuProps: MenuProps(backgroundColor: Colors.white),
searchFieldProps: TextFieldProps(
autofocus: true,
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Search Handler...",
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
// color: Colors.blue,
color: Color(0xFFEDF6F5),
width: 1.5,
),
),
),
),
// checkBoxBuilder: (context, item, isSelected, isDisabled) {
// return Icon(
// isSelected
// ? Icons.check_box
// : Icons.check_box_outline_blank,
// color: isSelected ? Color(0xFFE26728) : Colors.red,
// );
// },
// validationBuilder: (context, selectedItems) {
// return Padding(
// padding: const EdgeInsets.symmetric(
// vertical: 8.0,
// horizontal: 8.0,
// ),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Color(0xFF425B5B),
// foregroundColor: Colors.white,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8),
// ),
// minimumSize: Size(70, 36),
// ),
// onPressed: () => Navigator.pop(context),
// child: const Text("OK"),
// ),
// ],
// ),
// );
// },
),
onChanged: (List<Map<String, dynamic>> selectedVals) {
selectedHandlerIds = selectedVals
.map((v) => v['id'].toString())
.toList();
print("Selected Handler IDs: $selectedHandlerIds");
},
// popupProps: PopupProps.menu(
// fit: FlexFit.loose,
// constraints: BoxConstraints(maxHeight: 250),
// menuProps: MenuProps(
// backgroundColor:
// Colors.white, // 👈 sets dropdown background to white
// ),
// showSearchBox: true,
// searchFieldProps: TextFieldProps(
// decoration: InputDecoration(
// filled: true,
// fillColor: Colors.white,
// hintText: "Search Handler...",
// enabledBorder: OutlineInputBorder(
// borderSide: BorderSide(
// color: Colors.white,
// ), // 👈 Normal border
// ),
// focusedBorder: OutlineInputBorder(
// borderSide: BorderSide(
// color: Colors.white,
// width: 1.5,
// ), // 👈 Focused border
// ),
// ),
// ),
// // constraints: BoxConstraints(),
// ),
//
// onChanged: (val) {
// if (val != null) {
// print("Selected Handler : ${val['name']}");
// print("Id: ${val['id']}");
// selectedHandler = val['id'];
// // controllers['agentId']?.text = val['agent_code'];
// // agentId = agent['id'];
// }
// },
),
),
),
],
);
}
static final _textStyle = GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
);
}