sales executive
This commit is contained in:
parent
0054bfeb30
commit
3477cd99f3
File diff suppressed because one or more lines are too long
@ -19,6 +19,8 @@ import '../../presentation/screens/StaffAttendance/individual_Attendance.dart';
|
|||||||
import '../../presentation/screens/UserManagement/Agent/agent.dart';
|
import '../../presentation/screens/UserManagement/Agent/agent.dart';
|
||||||
import '../../presentation/screens/UserManagement/Agent/agentList.dart';
|
import '../../presentation/screens/UserManagement/Agent/agentList.dart';
|
||||||
import '../../presentation/screens/UserManagement/Profile/profile_mobile.dart';
|
import '../../presentation/screens/UserManagement/Profile/profile_mobile.dart';
|
||||||
|
import '../../presentation/screens/UserManagement/SalesExecutive/salesExecutive.dart';
|
||||||
|
import '../../presentation/screens/UserManagement/SalesExecutive/salesExecutiveList.dart';
|
||||||
import '../../presentation/screens/UserManagement/Staff/staff.dart';
|
import '../../presentation/screens/UserManagement/Staff/staff.dart';
|
||||||
import '../../presentation/screens/UserManagement/Staff/staffList.dart';
|
import '../../presentation/screens/UserManagement/Staff/staffList.dart';
|
||||||
import '../../presentation/screens/dashboard/dashboard.dart';
|
import '../../presentation/screens/dashboard/dashboard.dart';
|
||||||
@ -114,6 +116,24 @@ final GoRouter appRouter = GoRouter(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: AppRoutes.salesExecutiveLst,
|
||||||
|
// builder: (context, state) => const Agent(),
|
||||||
|
builder: (context, state) => const SalesExecutiveList(),
|
||||||
|
),
|
||||||
|
|
||||||
|
GoRoute(
|
||||||
|
path: '/salesExecutive/:id',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
// if "create", open empty form
|
||||||
|
if (id == 'create') {
|
||||||
|
return const SalesExecutive(); // create mode
|
||||||
|
} else {
|
||||||
|
return SalesExecutive(id: id); // update mode
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: AppRoutes.enquiryLst,
|
path: AppRoutes.enquiryLst,
|
||||||
|
|||||||
@ -8,6 +8,8 @@ class AppRoutes {
|
|||||||
static const agent = '/agent/:id';
|
static const agent = '/agent/:id';
|
||||||
static const String staffLst = '/staffLst';
|
static const String staffLst = '/staffLst';
|
||||||
static const staff = '/staff/:id';
|
static const staff = '/staff/:id';
|
||||||
|
static const String salesExecutiveLst = '/salesExecutiveLst';
|
||||||
|
static const salesExecutive = '/salesExecutive/:id';
|
||||||
static const String enquiryLst = '/enquiryLst';
|
static const String enquiryLst = '/enquiryLst';
|
||||||
static const String enquiryForStaff = '/enquiryForStaff';
|
static const String enquiryForStaff = '/enquiryForStaff';
|
||||||
static const String tabEnquiry = '/tabEnquiry';
|
static const String tabEnquiry = '/tabEnquiry';
|
||||||
|
|||||||
@ -428,7 +428,9 @@ class ApiService {
|
|||||||
};
|
};
|
||||||
final url;
|
final url;
|
||||||
|
|
||||||
if (role == 'staff') {
|
if (role == 'salesExecutive') {
|
||||||
|
url = Uri.parse('${Env.apiUrl}salesExecutive/changeExecutiveStatus');
|
||||||
|
} else if (role == 'staff') {
|
||||||
url = Uri.parse('${Env.apiUrl}staff/changeStaffStatus');
|
url = Uri.parse('${Env.apiUrl}staff/changeStaffStatus');
|
||||||
} else {
|
} else {
|
||||||
url = Uri.parse('${Env.apiUrl}/api/agent/changeAgentStatus');
|
url = Uri.parse('${Env.apiUrl}/api/agent/changeAgentStatus');
|
||||||
@ -557,6 +559,23 @@ class ApiService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> findProductivityDashboardData(id, broker) async {
|
||||||
|
// print(_token);
|
||||||
|
if (_token == null) {
|
||||||
|
await _initializeToken();
|
||||||
|
}
|
||||||
|
final url = Uri.parse(
|
||||||
|
'${Env.apiUrl}dashboard/productivityDashboard?manager_id=$id',
|
||||||
|
);
|
||||||
|
|
||||||
|
final headers = {
|
||||||
|
'Authorization': 'Bearer $_token' ?? '',
|
||||||
|
'app-signature': Env.App_Signature,
|
||||||
|
};
|
||||||
|
final response = await _makeGetRequest(url, headers);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------- AGENT ----------------------------------------------
|
// -------------------------------- AGENT ----------------------------------------------
|
||||||
|
|
||||||
Future<Map<String, dynamic>> fetchAgentIncentiveList(agentId) async {
|
Future<Map<String, dynamic>> fetchAgentIncentiveList(agentId) async {
|
||||||
@ -705,6 +724,57 @@ class ApiService {
|
|||||||
final response = await _makeGetRequest(url, headers);
|
final response = await _makeGetRequest(url, headers);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
// ------------------------------------ SALES EXECUTIVE -----------------------------------------------------
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> fetchSalesExecutiveList(
|
||||||
|
int managerId,
|
||||||
|
role,
|
||||||
|
) async {
|
||||||
|
// print(_token);
|
||||||
|
|
||||||
|
print('rolemanagerId - $managerId');
|
||||||
|
if (_token == null) {
|
||||||
|
await _initializeToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic url;
|
||||||
|
if (role == 'manager') {
|
||||||
|
print('manager');
|
||||||
|
// url = Uri.parse('${Env.apiUrl}staff/staffList');
|
||||||
|
url = Uri.parse(
|
||||||
|
'${Env.apiUrl}salesExecutive/executiveList?manager_id=${managerId}',
|
||||||
|
);
|
||||||
|
// url = Uri.parse('${Env.apiUrl}salesExecutive/executiveList');
|
||||||
|
} else {
|
||||||
|
print('handler');
|
||||||
|
url = Uri.parse(
|
||||||
|
'${Env.apiUrl}salesExecutive/executiveList?handler_id=${managerId}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final headers = {
|
||||||
|
'Authorization': 'Bearer $_token' ?? '',
|
||||||
|
'app-signature': Env.App_Signature,
|
||||||
|
};
|
||||||
|
final response = await _makeGetRequest(url, headers);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> findSingleSalesExecutiveData(id) async {
|
||||||
|
// print(_token);
|
||||||
|
if (_token == null) {
|
||||||
|
await _initializeToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
final url = Uri.parse('${Env.apiUrl}salesExecutive/findExecutive?id=$id');
|
||||||
|
// final url = Uri.parse('${Env.apiUrl}staff/findStaff?id=$id');
|
||||||
|
final headers = {
|
||||||
|
'Authorization': 'Bearer $_token' ?? '',
|
||||||
|
'app-signature': Env.App_Signature,
|
||||||
|
};
|
||||||
|
final response = await _makeGetRequest(url, headers);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------- ENQUIRY -------------------------------------------------
|
// ----------------------------------- ENQUIRY -------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@ -56,7 +56,8 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
|
|||||||
} else if (currentRoute.contains('enquiry')) {
|
} else if (currentRoute.contains('enquiry')) {
|
||||||
_activeMenu = 'Enquiry';
|
_activeMenu = 'Enquiry';
|
||||||
} else if (currentRoute.contains('agent') ||
|
} else if (currentRoute.contains('agent') ||
|
||||||
currentRoute.contains('staff')) {
|
currentRoute.contains('staff') ||
|
||||||
|
currentRoute.contains('salesExecutive')) {
|
||||||
_activeMenu = 'User';
|
_activeMenu = 'User';
|
||||||
} else if (currentRoute.contains('claim') ||
|
} else if (currentRoute.contains('claim') ||
|
||||||
currentRoute.contains('endosement') ||
|
currentRoute.contains('endosement') ||
|
||||||
@ -145,6 +146,15 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
|
|||||||
context.go(AppRoutes.staffLst);
|
context.go(AppRoutes.staffLst);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
_buildPopupItem(
|
||||||
|
label: "Sales Executive",
|
||||||
|
onTap: () {
|
||||||
|
_hidePopup();
|
||||||
|
setState(() => _activeMenu = 'User');
|
||||||
|
context.go(AppRoutes.salesExecutiveLst);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
if (key == 'Reports') ...[
|
if (key == 'Reports') ...[
|
||||||
if (role == 'manager') ...[
|
if (role == 'manager') ...[
|
||||||
|
|||||||
@ -481,8 +481,11 @@ class _AddDialogState extends State<AddDialog> {
|
|||||||
onChanged: filterData,
|
onChanged: filterData,
|
||||||
controller: _searchStaffController,
|
controller: _searchStaffController,
|
||||||
txtwidth: ResponsiveLayout.isMobile(context)
|
txtwidth: ResponsiveLayout.isMobile(context)
|
||||||
? MediaQuery.of(context).size.width * 0.7
|
? null
|
||||||
: MediaQuery.of(context).size.width * 0.15,
|
: MediaQuery.of(context).size.width * 0.26,
|
||||||
|
// txtwidth: ResponsiveLayout.isMobile(context)
|
||||||
|
// ? MediaQuery.of(context).size.width * 0.7
|
||||||
|
// : MediaQuery.of(context).size.width * 0.15,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
// import 'dart:io' as html;
|
// import 'dart:io' as html;
|
||||||
import 'dart:typed_data'; // Import for Uint8List
|
import 'dart:typed_data'; // Import for Uint8List
|
||||||
|
import 'package:dropdown_search/dropdown_search.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -20,10 +21,13 @@ import '../../../../data/utils/Pagination.dart';
|
|||||||
import '../../../../data/utils/toastNotification.dart';
|
import '../../../../data/utils/toastNotification.dart';
|
||||||
import '../../../../data/utils/validators.dart';
|
import '../../../../data/utils/validators.dart';
|
||||||
import '../../../layouts/main_layout.dart';
|
import '../../../layouts/main_layout.dart';
|
||||||
|
import '../../../layouts/responsive_layout.dart';
|
||||||
import '../../../providers/manager_provider.dart';
|
import '../../../providers/manager_provider.dart';
|
||||||
|
import '../../../providers/userRoleProvider.dart';
|
||||||
import '../../../providers/user_provider.dart';
|
import '../../../providers/user_provider.dart';
|
||||||
import '../../../themes/indicators/customizd_file_upload.dart';
|
import '../../../themes/indicators/customizd_file_upload.dart';
|
||||||
import '../../../themes/indicators/export_btn.dart';
|
import '../../../themes/indicators/export_btn.dart';
|
||||||
|
import '../../../themes/indicators/input_field_decoration.dart';
|
||||||
import '../../../themes/indicators/search_field_theme.dart';
|
import '../../../themes/indicators/search_field_theme.dart';
|
||||||
import '../../../themes/indicators/singleFileUpload.dart';
|
import '../../../themes/indicators/singleFileUpload.dart';
|
||||||
// import '../../../themes/indicators/upload_doc_theme.dart';
|
// import '../../../themes/indicators/upload_doc_theme.dart';
|
||||||
@ -48,10 +52,19 @@ class AgentState extends ConsumerState<Agent> {
|
|||||||
String? selectedId;
|
String? selectedId;
|
||||||
PlatformFile? docUploadedFile;
|
PlatformFile? docUploadedFile;
|
||||||
|
|
||||||
|
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKey =
|
||||||
|
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
|
||||||
|
|
||||||
Map<String, TextEditingController> controllers = {};
|
Map<String, TextEditingController> controllers = {};
|
||||||
String? _token;
|
String? _token;
|
||||||
dynamic userId;
|
dynamic userId;
|
||||||
dynamic managerId;
|
dynamic managerId;
|
||||||
|
dynamic role;
|
||||||
|
|
||||||
|
bool isLoading = false;
|
||||||
|
String? selectedSalesExectv;
|
||||||
|
List<Map<String, dynamic>> filteredSalesExecutiveData = [];
|
||||||
|
List<Map<String, dynamic>> getSalesExecutiveData = [];
|
||||||
|
|
||||||
Map<String, dynamic> dataDetails() {
|
Map<String, dynamic> dataDetails() {
|
||||||
final data = {
|
final data = {
|
||||||
@ -60,6 +73,7 @@ class AgentState extends ConsumerState<Agent> {
|
|||||||
"mobile": controllers["mobile"]?.text,
|
"mobile": controllers["mobile"]?.text,
|
||||||
"address": controllers["address"]?.text,
|
"address": controllers["address"]?.text,
|
||||||
"agent_code": controllers["code"]?.text,
|
"agent_code": controllers["code"]?.text,
|
||||||
|
'sales_executive_id': selectedSalesExectv,
|
||||||
"is_active": isActive,
|
"is_active": isActive,
|
||||||
"manager_id": managerId,
|
"manager_id": managerId,
|
||||||
};
|
};
|
||||||
@ -79,6 +93,12 @@ class AgentState extends ConsumerState<Agent> {
|
|||||||
Future.microtask(() {
|
Future.microtask(() {
|
||||||
managerId = ref.watch(managerIdProvider);
|
managerId = ref.watch(managerIdProvider);
|
||||||
userId = ref.watch(userIdProvider);
|
userId = ref.watch(userIdProvider);
|
||||||
|
|
||||||
|
role = ref.read(userRoleProvider);
|
||||||
|
print("E43 => mId: $managerId");
|
||||||
|
if (managerId != null && role != null) {
|
||||||
|
getRole(managerId, role);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +107,41 @@ class AgentState extends ConsumerState<Agent> {
|
|||||||
print("APISERTOKEN - $_token");
|
print("APISERTOKEN - $_token");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> getRole(int id, role) async {
|
||||||
|
print('getSalesExecutiveData called');
|
||||||
|
setState(() {
|
||||||
|
isLoading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await apiService.fetchSalesExecutiveList(id, role);
|
||||||
|
|
||||||
|
if (response['status'] == 'success') {
|
||||||
|
print('getRole - ${response['data']}');
|
||||||
|
setState(() {
|
||||||
|
getSalesExecutiveData = List<Map<String, dynamic>>.from(
|
||||||
|
response['data'],
|
||||||
|
);
|
||||||
|
print('API Data - $getSalesExecutiveData');
|
||||||
|
filteredSalesExecutiveData = getSalesExecutiveData
|
||||||
|
.where((item) => int.tryParse(item['is_active'].toString()) == 1)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
print('filteredSalesExecutiveData - $filteredSalesExecutiveData');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getSalesExecutiveData = [];
|
||||||
|
filteredSalesExecutiveData = [];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception occurred: $e');
|
||||||
|
} finally {
|
||||||
|
setState(() {
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void updateData() async {
|
void updateData() async {
|
||||||
if (widget.id != null && widget.id != 'create') {
|
if (widget.id != null && widget.id != 'create') {
|
||||||
dynamic response = await apiService.findSingleAgentData(widget.id!);
|
dynamic response = await apiService.findSingleAgentData(widget.id!);
|
||||||
@ -102,6 +157,7 @@ class AgentState extends ConsumerState<Agent> {
|
|||||||
controllers['code']?.text = data['agent_code'] ?? '';
|
controllers['code']?.text = data['agent_code'] ?? '';
|
||||||
controllers['address']?.text = data['address'] ?? '';
|
controllers['address']?.text = data['address'] ?? '';
|
||||||
isActive = data["is_active"];
|
isActive = data["is_active"];
|
||||||
|
selectedSalesExectv = data['sales_executive_id'] ?? '';
|
||||||
|
|
||||||
String? apiDocPath = data["certificate_file_name"];
|
String? apiDocPath = data["certificate_file_name"];
|
||||||
if (apiDocPath != null && apiDocPath.isNotEmpty) {
|
if (apiDocPath != null && apiDocPath.isNotEmpty) {
|
||||||
@ -470,12 +526,19 @@ class AgentState extends ConsumerState<Agent> {
|
|||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: buildAddress()),
|
Expanded(child: buildSalesExecutive(context)),
|
||||||
SizedBox(width: 25),
|
SizedBox(width: 25),
|
||||||
Expanded(child: buildUploadDocument()),
|
Expanded(child: buildAddress()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: buildUploadDocument()),
|
||||||
|
SizedBox(width: 25),
|
||||||
|
Expanded(child: SizedBox.shrink()),
|
||||||
|
],
|
||||||
|
),
|
||||||
// Row(
|
// Row(
|
||||||
// children: [
|
// children: [
|
||||||
// Expanded(child: buildIncentiveFile()),
|
// Expanded(child: buildIncentiveFile()),
|
||||||
@ -595,6 +658,157 @@ class AgentState extends ConsumerState<Agent> {
|
|||||||
borderColor: Color(0xFFE2E8F0),
|
borderColor: Color(0xFFE2E8F0),
|
||||||
highlightColor: Color(0xFF50A398),
|
highlightColor: Color(0xFF50A398),
|
||||||
txtwidth: MediaQuery.of(context).size.width * 0.26,
|
txtwidth: MediaQuery.of(context).size.width * 0.26,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'[ a-zA-Z0-9_#/.]')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildSalesExecutive(BuildContext context) {
|
||||||
|
Map<String, dynamic>? selectedroleVal = filteredSalesExecutiveData
|
||||||
|
.firstWhere(
|
||||||
|
(item) => item['id'].toString() == selectedSalesExectv,
|
||||||
|
orElse: () => {},
|
||||||
|
);
|
||||||
|
|
||||||
|
final isReadOnly = widget.id != null;
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("Sales Executive *", 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.26,
|
||||||
|
// height: 40,
|
||||||
|
child: AbsorbPointer(
|
||||||
|
absorbing: isReadOnly,
|
||||||
|
child: DropdownSearch<Map<String, dynamic>>(
|
||||||
|
key: dropDownKey,
|
||||||
|
selectedItem: selectedroleVal.isNotEmpty ? selectedroleVal : null,
|
||||||
|
items: (filter, infiniteScrollProps) {
|
||||||
|
return filteredSalesExecutiveData;
|
||||||
|
},
|
||||||
|
|
||||||
|
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;
|
||||||
|
// },
|
||||||
|
decoratorProps: DropDownDecoratorProps(
|
||||||
|
decoration:
|
||||||
|
AppInputDecorations.dropdownDecoration(
|
||||||
|
label: "Select Executive",
|
||||||
|
).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 Executive...",
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
itemBuilder: (context, item, isDisabled, isSelected) {
|
||||||
|
return Container(
|
||||||
|
color: isSelected
|
||||||
|
? Colors.blue.withOpacity(0.1)
|
||||||
|
: Colors.white,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 5, vertical: 5),
|
||||||
|
child: Text(
|
||||||
|
item['name'].toString(),
|
||||||
|
style: GoogleFonts.inter(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.black87,
|
||||||
|
fontWeight: isSelected
|
||||||
|
? FontWeight.w600
|
||||||
|
: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
// constraints: BoxConstraints(),
|
||||||
|
),
|
||||||
|
|
||||||
|
onChanged: widget.id != null
|
||||||
|
? null
|
||||||
|
: (val) {
|
||||||
|
if (val != null) {
|
||||||
|
print("Selected Role : ${val['role']}");
|
||||||
|
print("Id: ${val['id']}");
|
||||||
|
selectedSalesExectv = val['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// controllers['agentId']?.text = val['agent_code'];
|
||||||
|
// agentId = agent['id'];
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,928 @@
|
|||||||
|
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;
|
||||||
|
const SalesExecutive({super.key, this.id});
|
||||||
|
@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": userId,
|
||||||
|
};
|
||||||
|
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',
|
||||||
|
);
|
||||||
|
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 MainLayout(
|
||||||
|
title: "SalesExecutive",
|
||||||
|
body: Container(
|
||||||
|
// color: Colors.yellow.shade50,
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 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(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: buildName()),
|
||||||
|
SizedBox(width: 25),
|
||||||
|
Expanded(child: buildEmail()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: buildPhNumber()),
|
||||||
|
SizedBox(width: 25),
|
||||||
|
Expanded(child: buildAddress()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildName() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("Name *", style: _textStyle),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
ThemedFormField(
|
||||||
|
borderColor: Color(0xFFE2E8F0),
|
||||||
|
highlightColor: Color(0xFF50A398),
|
||||||
|
controller: controllers['name']!,
|
||||||
|
validator: (value) => Validators.requiredField(value, "name"),
|
||||||
|
txtwidth: MediaQuery.of(context).size.width * 0.26,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildEmail() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("Email *", style: _textStyle),
|
||||||
|
SizedBox(width: 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.26,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildPhNumber() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("Phone Number *", style: _textStyle),
|
||||||
|
SizedBox(width: 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.26,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildAddress() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text("Address", style: _textStyle),
|
||||||
|
SizedBox(width: 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.26,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildId() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
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.26,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.26,
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
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 Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
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.26,
|
||||||
|
// 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 Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
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.26,
|
||||||
|
// 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,473 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
|
||||||
|
|
||||||
|
import '../../../../core/routing/routes.dart';
|
||||||
|
import '../../../../core/services/api_service.dart';
|
||||||
|
import '../../../../data/utils/Pagination.dart';
|
||||||
|
import '../../../layouts/main_layout.dart';
|
||||||
|
import '../../../layouts/responsive_layout.dart';
|
||||||
|
import '../../../providers/manager_provider.dart';
|
||||||
|
import '../../../themes/indicators/export_btn.dart';
|
||||||
|
import '../../../themes/indicators/filter_btn.dart';
|
||||||
|
import '../../../themes/indicators/search_field_theme.dart';
|
||||||
|
import '../../../themes/indicators/text_field_theme.dart';
|
||||||
|
import '../../../widgets/custom_action_popup.dart';
|
||||||
|
|
||||||
|
class SalesExecutiveList extends ConsumerStatefulWidget {
|
||||||
|
const SalesExecutiveList({super.key});
|
||||||
|
@override
|
||||||
|
ConsumerState<SalesExecutiveList> createState() => SalesExecutiveListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SalesExecutiveListState extends ConsumerState<SalesExecutiveList> {
|
||||||
|
int currentPage = 1;
|
||||||
|
int itemsPerPage = 10;
|
||||||
|
late ApiService apiService;
|
||||||
|
dynamic managerId;
|
||||||
|
dynamic role;
|
||||||
|
dynamic prefid;
|
||||||
|
// List<Map<String, dynamic>> dataVal = [];
|
||||||
|
List<Map<String, dynamic>> getStaffData = [];
|
||||||
|
List<Map<String, dynamic>> originalData = [];
|
||||||
|
List<Map<String, dynamic>> filteredData = [];
|
||||||
|
bool isLoading = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
apiService = ApiService();
|
||||||
|
|
||||||
|
Future.microtask(() {
|
||||||
|
final data1 = ref.read(managerIdProvider);
|
||||||
|
// final data2 = ref.read(handlerIdProvider);
|
||||||
|
print("Edata1 => mId: $data1 -2 :");
|
||||||
|
prefid = data1;
|
||||||
|
role = ref.read(userRoleProvider);
|
||||||
|
print("E43 => mId: $prefid");
|
||||||
|
if (prefid != null && role != null) {
|
||||||
|
getSalesExecutiveList(prefid, role);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// getSalesExecutiveList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<dynamic> get _paginatedData {
|
||||||
|
// Sort descending by id first
|
||||||
|
final sortedData = [...filteredData]
|
||||||
|
..sort((a, b) => int.parse(b['id']) - int.parse(a['id']));
|
||||||
|
|
||||||
|
if (sortedData.isEmpty) return [];
|
||||||
|
|
||||||
|
// Ensure currentPage is valid
|
||||||
|
final maxPage = (sortedData.length / itemsPerPage).ceil();
|
||||||
|
final safePage = currentPage.clamp(1, maxPage);
|
||||||
|
|
||||||
|
final startIndex = (safePage - 1) * itemsPerPage;
|
||||||
|
final endIndex = (startIndex + itemsPerPage).clamp(0, sortedData.length);
|
||||||
|
|
||||||
|
return sortedData.sublist(startIndex, endIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void filterData(String query) {
|
||||||
|
setState(() {
|
||||||
|
final q = query.toLowerCase();
|
||||||
|
|
||||||
|
if (q.isEmpty) {
|
||||||
|
filteredData = getStaffData;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredData = getStaffData.where((item) {
|
||||||
|
final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive";
|
||||||
|
|
||||||
|
return (item['name'] ?? '').toString().toLowerCase().contains(q) ||
|
||||||
|
(item['email'] ?? '').toString().toLowerCase().contains(q) ||
|
||||||
|
(item['mobile'] ?? '').toString().toLowerCase().contains(q) ||
|
||||||
|
(item['address'] ?? '').toString().toLowerCase().contains(q) ||
|
||||||
|
(item['emp_id'] ?? item['agent_code'] ?? '')
|
||||||
|
.toString()
|
||||||
|
.toLowerCase()
|
||||||
|
.contains(q) ||
|
||||||
|
isActiveStatus.contains(q);
|
||||||
|
}).toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final TextEditingController _searchStaffController = TextEditingController();
|
||||||
|
|
||||||
|
Future<void> getSalesExecutiveList(int id, role) async {
|
||||||
|
print('E104 => Fns called => $id');
|
||||||
|
print('E104 => Fns role => $role');
|
||||||
|
setState(() {
|
||||||
|
isLoading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await apiService.fetchSalesExecutiveList(id, role);
|
||||||
|
|
||||||
|
if (response['status'] == 'success') {
|
||||||
|
print('E113 => getSalesExecutiveListData => ${response['data']}');
|
||||||
|
setState(() {
|
||||||
|
getStaffData = List<Map<String, dynamic>>.from(response['data']);
|
||||||
|
originalData = getStaffData;
|
||||||
|
filteredData = List.from(originalData);
|
||||||
|
// print('originalData - $getClaimPolicies');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getStaffData = [];
|
||||||
|
originalData = [];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception occurred: $e');
|
||||||
|
} finally {
|
||||||
|
setState(() {
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
managerId = ref.watch(managerIdProvider);
|
||||||
|
|
||||||
|
return MainLayout(
|
||||||
|
title: "Sales Executive",
|
||||||
|
body: Container(
|
||||||
|
// padding: EdgeInsets.all(8.0),
|
||||||
|
// margin: EdgeInsets.all(10.0),
|
||||||
|
|
||||||
|
// color: Colors.yellow.shade50,
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
// color: Colors.green,
|
||||||
|
// color: Colors.green.shade50,
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
// margin: EdgeInsets.all(10.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
// color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(15.0),
|
||||||
|
),
|
||||||
|
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
// height: 40,
|
||||||
|
// color: Colors.pink,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Sales Executive List',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
ThemedSearchField(
|
||||||
|
hintText: 'Search',
|
||||||
|
// backgroundColor: Color(0xFFF6F8F8),
|
||||||
|
backgroundColor: Color(0xFFFFFFFF),
|
||||||
|
txtHeight: 30,
|
||||||
|
onChanged: filterData,
|
||||||
|
controller: _searchStaffController,
|
||||||
|
txtwidth: MediaQuery.of(context).size.width * 0.15,
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(width: 10),
|
||||||
|
ExportBtn(
|
||||||
|
sheetName: "SalesExecutive",
|
||||||
|
fileName: "salesExecutive_list",
|
||||||
|
txt: !ResponsiveLayout.isMobile(context)
|
||||||
|
? true
|
||||||
|
: false,
|
||||||
|
data: filteredData,
|
||||||
|
displayHeaders: [
|
||||||
|
'S.No.',
|
||||||
|
'Staff Name',
|
||||||
|
'Email',
|
||||||
|
'Phone Number',
|
||||||
|
'Address',
|
||||||
|
'Status',
|
||||||
|
],
|
||||||
|
keys: [
|
||||||
|
"sno", // handled internally as i + 1
|
||||||
|
"name",
|
||||||
|
"email",
|
||||||
|
"mobile",
|
||||||
|
"address",
|
||||||
|
"is_active",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(width: 10),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
context.go('/salesExecutive/create');
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(4.8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF2E7D6E),
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Tooltip(
|
||||||
|
message: 'Create Sales Executive',
|
||||||
|
child: Icon(
|
||||||
|
Icons.add,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Color(0xFFF1F5F9),
|
||||||
|
|
||||||
|
// color: Color(0xFFEDF6F5),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 10,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Text('S.No.', style: _headerStyle),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text('Executive Name', style: _headerStyle),
|
||||||
|
),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Text('Email', style: _headerStyle),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text('Phone Number', style: _headerStyle),
|
||||||
|
),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Text('Status', style: _headerStyle),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Text('Action', style: _headerStyle),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(child: _buildDataTable(context)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Container(
|
||||||
|
// height: 20,
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
// color: Colors.green.shade50,
|
||||||
|
child: PaginationControls(
|
||||||
|
currentPage: currentPage,
|
||||||
|
itemsPerPage: itemsPerPage,
|
||||||
|
// totalItems: dataVal.length,
|
||||||
|
totalItems: filteredData.length,
|
||||||
|
// activeColor: layoutColor, // your theme color
|
||||||
|
onPageChanged: (page) {
|
||||||
|
setState(() {
|
||||||
|
currentPage = page;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onItemsPerPageChanged: (items) {
|
||||||
|
setState(() {
|
||||||
|
itemsPerPage = items;
|
||||||
|
currentPage = 1;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDataTable(BuildContext context) {
|
||||||
|
if (filteredData.isEmpty) {
|
||||||
|
return const SizedBox(
|
||||||
|
height: 50,
|
||||||
|
child: Center(child: Text('No available data')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final sortedData = [..._paginatedData];
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
// itemCount: filteredData.length + 1, // +1 for header, +1 for pagination
|
||||||
|
itemCount: sortedData.length + 1, // +1 for header, +1 for pagination
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == 0) return _buildHeader();
|
||||||
|
// if (index == dataVal.length + 1)
|
||||||
|
// return _buildPagination(context);
|
||||||
|
final startIndex = ((currentPage - 1) * itemsPerPage);
|
||||||
|
// final item = filteredData[index - 1];
|
||||||
|
final item = sortedData[index - 1];
|
||||||
|
final sno = startIndex + index;
|
||||||
|
return _buildDataRow(item, sno);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader() {
|
||||||
|
return SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDataRow(Map<String, dynamic> item, sno) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 1, horizontal: 16),
|
||||||
|
// margin: const EdgeInsets.only(top: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
// color: Color(0xFFE0F7F9),
|
||||||
|
border: const Border(
|
||||||
|
bottom: BorderSide(color: Colors.blueGrey, width: 0.15),
|
||||||
|
),
|
||||||
|
// borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(flex: 1, child: Text('$sno' ?? '-', style: _dataBold)),
|
||||||
|
Expanded(flex: 2, child: Text(item['name'] ?? '-', style: _dataBold)),
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Text(item['email'] ?? '-', style: _dataBold),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(item['mobile'] ?? '-', style: _dataBold),
|
||||||
|
),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
// color: Colors.yellow.shade50,
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 0.4, // reduce size (0.7–0.9 works well)
|
||||||
|
child: Switch(
|
||||||
|
value: item['is_active'] == "1",
|
||||||
|
onChanged: (val) {
|
||||||
|
setState(() {
|
||||||
|
item['is_active'] = val ? "1" : "0";
|
||||||
|
});
|
||||||
|
final response = apiService.updateStatus(
|
||||||
|
item['id'],
|
||||||
|
val ? "0" : "1",
|
||||||
|
'salesExecutive',
|
||||||
|
);
|
||||||
|
print("Response - $response");
|
||||||
|
},
|
||||||
|
activeColor: Color(0xFF2E7D6E), // thumb when active
|
||||||
|
activeTrackColor: Color(0xFFDCFCE7), // track when active
|
||||||
|
// activeColor: Color(0xFF425B5B), // thumb when active
|
||||||
|
// activeTrackColor: Color(0xFFB2D8D3), // track when active
|
||||||
|
inactiveThumbColor:
|
||||||
|
Colors.grey.shade400, // thumb when inactive
|
||||||
|
inactiveTrackColor:
|
||||||
|
Colors.grey.shade300, // track when inactive
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: item['is_active'] == "1"
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
Tooltip(
|
||||||
|
message: 'Edit',
|
||||||
|
child: IconButton(
|
||||||
|
icon: Image.asset(
|
||||||
|
"assets/miscellaneous/Edit.png",
|
||||||
|
height: 12,
|
||||||
|
width: 15,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
print('EDITStaff - ${item['id']}');
|
||||||
|
dynamic id = item['id'];
|
||||||
|
context.go('/salesExecutive/$id');
|
||||||
|
},
|
||||||
|
splashRadius: 28,
|
||||||
|
hoverColor: Colors.black12,
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
"assets/miscellaneous/Edit_muted.png",
|
||||||
|
height: 12,
|
||||||
|
width: 15,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final _dataBold = GoogleFonts.inter(
|
||||||
|
fontSize: 12,
|
||||||
|
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Color(0xFF000000),
|
||||||
|
);
|
||||||
|
|
||||||
|
static final _dataSub = GoogleFonts.inter(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w300,
|
||||||
|
color: Color(0xFF585757),
|
||||||
|
);
|
||||||
|
|
||||||
|
static final _headerStyle = GoogleFonts.poppins(
|
||||||
|
fontSize: 11.2,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Color(0xFF1E293B),
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import 'package:nhance_partner/presentation/screens/dashboard/tabs/chart.dart';
|
|||||||
import 'package:nhance_partner/presentation/screens/dashboard/tabs/live.dart';
|
import 'package:nhance_partner/presentation/screens/dashboard/tabs/live.dart';
|
||||||
import 'package:nhance_partner/presentation/screens/dashboard/tabs/partner.dart';
|
import 'package:nhance_partner/presentation/screens/dashboard/tabs/partner.dart';
|
||||||
import 'package:nhance_partner/presentation/screens/dashboard/tabs/productivity.dart';
|
import 'package:nhance_partner/presentation/screens/dashboard/tabs/productivity.dart';
|
||||||
|
import 'package:nhance_partner/presentation/screens/dashboard/tabs/stacked_BAR_Exple.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../../../core/responsive/responsive_builder.dart';
|
import '../../../core/responsive/responsive_builder.dart';
|
||||||
import '../../../core/routing/routes.dart';
|
import '../../../core/routing/routes.dart';
|
||||||
@ -135,7 +136,8 @@ class _DashboardState extends ConsumerState<Dashboard>
|
|||||||
LiveDashboard(),
|
LiveDashboard(),
|
||||||
BussinessDashboard(),
|
BussinessDashboard(),
|
||||||
PartnerDashboard(),
|
PartnerDashboard(),
|
||||||
ProductiviyDashboard(),
|
ProductivityDashboard(),
|
||||||
|
// BarChartSample4(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -275,38 +275,69 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
// mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
Text('Hide and Show Charts : ', style: _styleSmall1),
|
Text('Hide and Show Charts : ', style: _styleSmall1),
|
||||||
|
|
||||||
TextButton(
|
Row(
|
||||||
onPressed: () {
|
children: [
|
||||||
setState(() => showInsurer = !showInsurer);
|
Transform.scale(
|
||||||
},
|
scale: 0.75,
|
||||||
child: Text(
|
child: Checkbox(
|
||||||
showInsurer ? "Insurer" : "Insurer Hidden",
|
value: showInsurer,
|
||||||
style: _styleSmall2,
|
onChanged: (val) {
|
||||||
),
|
setState(() => showInsurer = val!);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
// showInsurer ? "Insurer" : "Insurer Hidden",
|
||||||
|
"Insurer",
|
||||||
|
style: _styleSmall2,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
SizedBox(width: 10),
|
||||||
setState(() => showBroker = !showBroker);
|
|
||||||
},
|
Row(
|
||||||
child: Text(
|
children: [
|
||||||
showBroker ? "Broker" : "Broker Hidden",
|
Transform.scale(
|
||||||
style: _styleSmall2,
|
scale: 0.75,
|
||||||
),
|
child: Checkbox(
|
||||||
|
value: showBroker,
|
||||||
|
onChanged: (val) {
|
||||||
|
setState(() => showBroker = val!);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
// showBroker ? "Broker" : "Broker Hidden",
|
||||||
|
"Broker",
|
||||||
|
style: _styleSmall2,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
SizedBox(width: 10),
|
||||||
setState(() => showProduct = !showProduct);
|
|
||||||
},
|
Row(
|
||||||
child: Text(
|
children: [
|
||||||
showProduct ? "Product " : "Product Hidden",
|
Transform.scale(
|
||||||
style: _styleSmall2,
|
scale: 0.75,
|
||||||
),
|
child: Checkbox(
|
||||||
|
value: showProduct,
|
||||||
|
onChanged: (val) {
|
||||||
|
setState(() => showProduct = val!);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
// showProduct ? "Product" : "Product Hidden",
|
||||||
|
"Product",
|
||||||
|
style: _styleSmall2,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -334,6 +365,7 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.all(10),
|
padding: EdgeInsets.all(10),
|
||||||
margin: EdgeInsets.only(right: 20),
|
margin: EdgeInsets.only(right: 20),
|
||||||
|
clipBehavior: Clip.none,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
@ -364,9 +396,13 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: CustomBarChart(
|
child: Container(
|
||||||
dataList: chartInsurerData,
|
clipBehavior: Clip.none,
|
||||||
labels: chartInsurerLabels,
|
// color: Colors.red.shade100,
|
||||||
|
child: CustomBarChart(
|
||||||
|
dataList: chartInsurerData,
|
||||||
|
labels: chartInsurerLabels,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
legendWidget(),
|
legendWidget(),
|
||||||
|
|||||||
@ -9,52 +9,51 @@ import '../../../../core/services/api_service.dart';
|
|||||||
import '../../../../data/models/bar_data.dart';
|
import '../../../../data/models/bar_data.dart';
|
||||||
import '../../../providers/userRoleProvider.dart';
|
import '../../../providers/userRoleProvider.dart';
|
||||||
import '../../../themes/charts/barChart.dart';
|
import '../../../themes/charts/barChart.dart';
|
||||||
|
import '../../../themes/charts/stackedBarChart.dart';
|
||||||
|
|
||||||
class ProductiviyDashboard extends ConsumerStatefulWidget {
|
class ProductivityDashboard extends ConsumerStatefulWidget {
|
||||||
const ProductiviyDashboard({super.key});
|
const ProductivityDashboard({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<ProductiviyDashboard> createState() =>
|
ConsumerState<ProductivityDashboard> createState() =>
|
||||||
_ProductiviyDashboardState();
|
_ProductivityDashboardState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
|
||||||
dynamic managerId;
|
dynamic managerId;
|
||||||
late ApiService apiService;
|
late ApiService apiService;
|
||||||
|
|
||||||
// List<Map<String, dynamic>> dataVal = [];
|
// List<Map<String, dynamic>> dataVal = [];
|
||||||
List<Map<String, dynamic>> getBusinessDshBdData = [];
|
List<Map<String, dynamic>> getProductivityDshBdData = [];
|
||||||
List<Map<String, dynamic>> originalData = [];
|
List<Map<String, dynamic>> originalData = [];
|
||||||
List<Map<String, dynamic>> filteredData = [];
|
List<Map<String, dynamic>> filteredData = [];
|
||||||
|
|
||||||
List<Map<String, dynamic>> brokerWise = [];
|
|
||||||
List<Map<String, dynamic>> productWise = [];
|
List<Map<String, dynamic>> productWise = [];
|
||||||
List<Map<String, dynamic>> insurerWise = [];
|
List<Map<String, dynamic>> insurerWise = [];
|
||||||
|
|
||||||
List<Map<String, dynamic>> originalBrokerWise = [];
|
|
||||||
List<Map<String, dynamic>> filteredBrokerWise = [];
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> originalProductWise = [];
|
List<Map<String, dynamic>> originalProductWise = [];
|
||||||
List<Map<String, dynamic>> filteredProductWise = [];
|
List<Map<String, dynamic>> filteredProductWise = [];
|
||||||
|
|
||||||
List<Map<String, dynamic>> originalInsurerWise = [];
|
List<Map<String, dynamic>> originalStaffWise = [];
|
||||||
List<Map<String, dynamic>> filteredInsurerWise = [];
|
List<Map<String, dynamic>> filteredStaffWise = [];
|
||||||
|
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
bool isInsureWisePolicy = true;
|
bool isInsureWisePolicy = true;
|
||||||
bool isBrokerWisePolicy = true;
|
|
||||||
bool isProductWisePolicy = true;
|
bool isProductWisePolicy = true;
|
||||||
|
|
||||||
bool showInsurer = true;
|
bool showStaff = true;
|
||||||
bool showBroker = true;
|
|
||||||
bool showProduct = true;
|
bool showProduct = true;
|
||||||
|
|
||||||
|
List<String> staffLabels = [];
|
||||||
|
List<List<List<dynamic>>> staffChartData = [];
|
||||||
|
double maxPremium = 0;
|
||||||
|
|
||||||
int selectedView = 0;
|
int selectedView = 0;
|
||||||
// 0 = Insurer
|
// 0 = Staff
|
||||||
// 1 = Broker
|
// 1 = Broker
|
||||||
// 2 = Product
|
// 2 = Product
|
||||||
|
|
||||||
String? SelctdBusinessbroker;
|
String? SelctdProductivityproduct;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -70,54 +69,115 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
|||||||
// print("ENQroleId - $roleId");
|
// print("ENQroleId - $roleId");
|
||||||
|
|
||||||
if (managerId != null) {
|
if (managerId != null) {
|
||||||
getBusinessDashBrdList(managerId);
|
getProductivityDashBrdList(managerId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getBusinessDashBrdList(id) async {
|
Future<void> getProductivityDashBrdList(id) async {
|
||||||
// print('getClaimList called MagId - $managerId - $role');
|
// print('getClaimList called MagId - $managerId - $role');
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
});
|
});
|
||||||
// dynamic id = widget.policyNumber;
|
// dynamic id = widget.policyNumber;
|
||||||
print('getBusinessDshID - $id');
|
print('getProductivityDshID - $id');
|
||||||
try {
|
try {
|
||||||
final response = await apiService.findBusinessDashboardData(
|
final response = await apiService.findProductivityDashboardData(
|
||||||
id,
|
id,
|
||||||
SelctdBusinessbroker,
|
SelctdProductivityproduct,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response['status'] == 'success') {
|
if (response['status'] == 'success') {
|
||||||
final data = response['data'];
|
final data = response['data'];
|
||||||
print('getBusinessDshBdData - ${response['data']}');
|
print('getProductivityDshBdData - ${response['data']}');
|
||||||
setState(() {
|
setState(() {
|
||||||
if (data is List) {
|
if (data is Map<String, dynamic>) {
|
||||||
// Already a list of maps
|
staffLabels.clear();
|
||||||
getBusinessDshBdData = List<Map<String, dynamic>>.from(data);
|
staffChartData.clear();
|
||||||
} else if (data is Map) {
|
maxPremium = 0;
|
||||||
// Single object, wrap in a list
|
|
||||||
getBusinessDshBdData = [Map<String, dynamic>.from(data)];
|
final staffWise = (data['StaffWise'] ?? []) as List<dynamic>;
|
||||||
} else {
|
|
||||||
getBusinessDshBdData = [];
|
staffLabels.clear();
|
||||||
|
staffChartData.clear();
|
||||||
|
maxPremium = 0.0;
|
||||||
|
|
||||||
|
double _toDouble(dynamic v) {
|
||||||
|
if (v == null) return 0.0;
|
||||||
|
if (v is double) return v;
|
||||||
|
if (v is int) return v.toDouble();
|
||||||
|
return double.tryParse(v.toString()) ?? 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var staff in staffWise) {
|
||||||
|
final name = staff['sales_executive_name']?.toString() ?? '';
|
||||||
|
staffLabels.add(name);
|
||||||
|
|
||||||
|
double totalPre = 0.0;
|
||||||
|
double totalCur = 0.0;
|
||||||
|
|
||||||
|
final products = (staff['products_list'] ?? []) as List<dynamic>;
|
||||||
|
for (var p in products) {
|
||||||
|
totalPre += _toDouble(p['total_premium_pre_month']);
|
||||||
|
totalCur += _toDouble(p['total_premium_current_month']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// build 3 stacks: preMonth, currentMonth (stacked after pre), extra (difference optional)
|
||||||
|
final preStart = 0.0;
|
||||||
|
final preEnd = totalPre;
|
||||||
|
final curStart = preEnd;
|
||||||
|
final curEnd = preEnd + totalCur;
|
||||||
|
// optional extra stack (here zero, but kept for structure)
|
||||||
|
final extraStart = curEnd;
|
||||||
|
final extraEnd = curEnd; // no extra by default
|
||||||
|
|
||||||
|
// update chart max
|
||||||
|
maxPremium = [maxPremium, curEnd].reduce((a, b) => a > b ? a : b);
|
||||||
|
|
||||||
|
staffChartData.add([
|
||||||
|
[preStart, preEnd, Colors.cyan],
|
||||||
|
[curStart, curEnd, Colors.blueAccent],
|
||||||
|
// include extra only if needed >0
|
||||||
|
if ((extraEnd - extraStart) > 0)
|
||||||
|
[extraStart, extraEnd, Colors.grey],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// use setState to save and pass to chart
|
||||||
|
setState(() {
|
||||||
|
originalStaffWise = List.from(staffWise); // if you need
|
||||||
|
staffLabels = staffLabels;
|
||||||
|
staffChartData = staffChartData;
|
||||||
|
// choose a little padding for maxY
|
||||||
|
final chartMax = (maxPremium * 1.1).ceilToDouble();
|
||||||
|
// store chartMax somewhere e.g. chartMaxY
|
||||||
|
});
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
}
|
}
|
||||||
// getBusinessDshBdData = List<Map<String, dynamic>>.from(response['data']);
|
|
||||||
originalData = getBusinessDshBdData;
|
// if (data is List) {
|
||||||
filteredData = List.from(originalData);
|
// // Already a list of maps
|
||||||
// print('originalData - $getClaimPolicies');
|
// getProductivityDshBdData = List<Map<String, dynamic>>.from(data);
|
||||||
|
// } else if (data is Map) {
|
||||||
|
// // Single object, wrap in a list
|
||||||
|
// getProductivityDshBdData = [Map<String, dynamic>.from(data)];
|
||||||
|
// } else {
|
||||||
|
// getProductivityDshBdData = [];
|
||||||
|
// }
|
||||||
|
// // getProductivityDshBdData = List<Map<String, dynamic>>.from(response['data']);
|
||||||
|
// originalData = getProductivityDshBdData;
|
||||||
|
// filteredData = List.from(originalData);
|
||||||
|
// // print('originalData - $getClaimPolicies');
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data is Map<String, dynamic>) {
|
if (data is Map<String, dynamic>) {
|
||||||
setState(() {
|
setState(() {
|
||||||
// Store BrokerWise
|
// Store BrokerWise
|
||||||
brokerWise = List<Map<String, dynamic>>.from(
|
productWise = List<Map<String, dynamic>>.from(
|
||||||
data['brokerWise'] ?? [],
|
data['productWise'] ?? [],
|
||||||
);
|
);
|
||||||
|
|
||||||
print('brokerWise - ${brokerWise}');
|
|
||||||
originalBrokerWise = List.from(brokerWise);
|
|
||||||
filteredBrokerWise = List.from(brokerWise);
|
|
||||||
|
|
||||||
// Store ProductWise
|
// Store ProductWise
|
||||||
productWise = List<Map<String, dynamic>>.from(
|
productWise = List<Map<String, dynamic>>.from(
|
||||||
data['productWise'] ?? [],
|
data['productWise'] ?? [],
|
||||||
@ -127,22 +187,22 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
|||||||
originalProductWise = List.from(productWise);
|
originalProductWise = List.from(productWise);
|
||||||
filteredProductWise = List.from(productWise);
|
filteredProductWise = List.from(productWise);
|
||||||
|
|
||||||
// Store InsurerWise
|
// Store StaffWise
|
||||||
insurerWise = List<Map<String, dynamic>>.from(
|
insurerWise = List<Map<String, dynamic>>.from(
|
||||||
data['insurerWise'] ?? [],
|
data['insurerWise'] ?? [],
|
||||||
);
|
);
|
||||||
print('insurerWise - ${insurerWise}');
|
print('insurerWise - ${insurerWise}');
|
||||||
originalInsurerWise = List.from(insurerWise);
|
originalStaffWise = List.from(insurerWise);
|
||||||
filteredInsurerWise = List.from(insurerWise);
|
filteredStaffWise = List.from(insurerWise);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// fallback
|
// fallback
|
||||||
brokerWise = [];
|
productWise = [];
|
||||||
productWise = [];
|
productWise = [];
|
||||||
insurerWise = [];
|
insurerWise = [];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
getBusinessDshBdData = [];
|
getProductivityDshBdData = [];
|
||||||
originalData = [];
|
originalData = [];
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -154,7 +214,7 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BarData> buildInsurerBarData() {
|
List<BarData> buildStaffBarData() {
|
||||||
double parseDouble(dynamic value) {
|
double parseDouble(dynamic value) {
|
||||||
if (value == null) return 0.0;
|
if (value == null) return 0.0;
|
||||||
if (value is num) return value.toDouble();
|
if (value is num) return value.toDouble();
|
||||||
@ -162,7 +222,7 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
|||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return filteredInsurerWise.map((item) {
|
return filteredStaffWise.map((item) {
|
||||||
print('policies_current - ${item['total_policies_current_month']}');
|
print('policies_current - ${item['total_policies_current_month']}');
|
||||||
print('policies_PREV - ${item['total_policies_pre_month']}');
|
print('policies_PREV - ${item['total_policies_pre_month']}');
|
||||||
|
|
||||||
@ -181,8 +241,8 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
|||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> buildInsurerLabels() {
|
List<String> buildStaffLabels() {
|
||||||
return filteredInsurerWise.map<String>((item) {
|
return filteredStaffWise.map<String>((item) {
|
||||||
final name = item['short_name'];
|
final name = item['short_name'];
|
||||||
if (name != null && name.toString().trim().isNotEmpty) {
|
if (name != null && name.toString().trim().isNotEmpty) {
|
||||||
return name.toString();
|
return name.toString();
|
||||||
@ -192,44 +252,6 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
|||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BarData> buildBrokerBarData() {
|
|
||||||
double parseDouble(dynamic value) {
|
|
||||||
if (value == null) return 0.0;
|
|
||||||
if (value is num) return value.toDouble();
|
|
||||||
if (value is String) return double.tryParse(value) ?? 0.0;
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredBrokerWise.map((item) {
|
|
||||||
print('policies_current - ${item['total_policies_current_month']}');
|
|
||||||
print('policies_PREV - ${item['total_policies_pre_month']}');
|
|
||||||
|
|
||||||
print('premium_current - ${item['total_premium_current_month']}');
|
|
||||||
print('premium_PREV - ${item['total_premium_pre_month']}');
|
|
||||||
return BarData(
|
|
||||||
Colors.blue,
|
|
||||||
isBrokerWisePolicy
|
|
||||||
? parseDouble(item['total_policies_current_month'])
|
|
||||||
: parseDouble(item['total_premium_current_month']),
|
|
||||||
isBrokerWisePolicy
|
|
||||||
? parseDouble(item['total_policies_pre_month'])
|
|
||||||
: parseDouble(item['total_premium_pre_month']),
|
|
||||||
5,
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> buildBrokerLabels() {
|
|
||||||
return filteredBrokerWise.map<String>((item) {
|
|
||||||
final name = item['broker_name'];
|
|
||||||
if (name != null && name.toString().trim().isNotEmpty) {
|
|
||||||
return name.toString();
|
|
||||||
} else {
|
|
||||||
return item['broker_name']?.toString() ?? '';
|
|
||||||
}
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<BarData> buildProductBarData() {
|
List<BarData> buildProductBarData() {
|
||||||
double parseDouble(dynamic value) {
|
double parseDouble(dynamic value) {
|
||||||
if (value == null) return 0.0;
|
if (value == null) return 0.0;
|
||||||
@ -274,12 +296,196 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
|||||||
margin: EdgeInsets.only(left: 10, right: 10, bottom: 10),
|
margin: EdgeInsets.only(left: 10, right: 10, bottom: 10),
|
||||||
// color: Colors.amber.shade100,
|
// color: Colors.amber.shade100,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
children: [
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
Row(
|
||||||
children: [Text('No Data Found ', style: _styleSmall1)],
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text('Hide and Show Charts : ', style: _styleSmall1),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Transform.scale(
|
||||||
|
scale: 0.75,
|
||||||
|
child: Checkbox(
|
||||||
|
value: showStaff,
|
||||||
|
onChanged: (val) {
|
||||||
|
setState(() => showStaff = val!);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
// showStaff ? "Staff" : "Staff Hidden",
|
||||||
|
"Staff",
|
||||||
|
style: _styleSmall2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// SizedBox(width: 10),
|
||||||
|
//
|
||||||
|
// Row(
|
||||||
|
// children: [
|
||||||
|
// Transform.scale(
|
||||||
|
// scale: 0.75,
|
||||||
|
// child: Checkbox(
|
||||||
|
// value: showProduct,
|
||||||
|
// onChanged: (val) {
|
||||||
|
// setState(() => showProduct = val!);
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// Text(
|
||||||
|
// // showBroker ? "Broker" : "Broker Hidden",
|
||||||
|
// "Product",
|
||||||
|
// style: _styleSmall2,
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
showStaff ? Expanded(child: insurerContainer()) : SizedBox(),
|
||||||
|
// showProduct ? Expanded(child: productContainer()) : SizedBox(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget insurerContainer() {
|
||||||
|
final chartStaffData = buildStaffBarData();
|
||||||
|
final chartStaffLabels = buildStaffLabels();
|
||||||
|
print('chartStaffData - $chartStaffData');
|
||||||
|
print('chartStaffLabels - $chartStaffLabels');
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(10),
|
||||||
|
margin: EdgeInsets.only(right: 20),
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Staff Wise (Previous / Current Month)',
|
||||||
|
style: _chartHeader,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
isInsureWisePolicy ? "Policies" : "Premium",
|
||||||
|
style: _styleSmall1,
|
||||||
|
),
|
||||||
|
Transform.scale(
|
||||||
|
scale: 0.5,
|
||||||
|
child: Switch(
|
||||||
|
value: isInsureWisePolicy,
|
||||||
|
onChanged: (val) => setState(() => isInsureWisePolicy = val),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
|
||||||
|
// Expanded(
|
||||||
|
// child: Container(
|
||||||
|
// clipBehavior: Clip.none,
|
||||||
|
// // color: Colors.red.shade100,
|
||||||
|
// child: CustomStackedBarChart(
|
||||||
|
// labels: staffLabels, // 👈 staff names
|
||||||
|
// maxY: maxPremium + 5000, // avoid touching border
|
||||||
|
// data: staffChartData,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
Expanded(
|
||||||
|
child: CustomStackedBarChart(
|
||||||
|
labels: staffLabels,
|
||||||
|
data: staffChartData,
|
||||||
|
maxY: (maxPremium * 1.1).ceilToDouble(), // a bit of headroom
|
||||||
|
initialRotation: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
legendWidget(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget productContainer() {
|
||||||
|
final chartProductData = buildProductBarData();
|
||||||
|
final chartProductLabels = buildProductLabels();
|
||||||
|
print('chartProductData - $chartProductData');
|
||||||
|
print('chartProductLabels - $chartProductLabels');
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(10),
|
||||||
|
margin: EdgeInsets.only(right: 20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Staff Product Wise (Previous / Current Month)',
|
||||||
|
style: _chartHeader,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
isProductWisePolicy ? "Policies" : "Premium",
|
||||||
|
style: _styleSmall1,
|
||||||
|
),
|
||||||
|
Transform.scale(
|
||||||
|
scale: 0.5,
|
||||||
|
child: Switch(
|
||||||
|
value: isProductWisePolicy,
|
||||||
|
onChanged: (val) => setState(() => isProductWisePolicy = val),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Expanded(
|
||||||
|
child: CustomBarChart(
|
||||||
|
dataList: chartProductData,
|
||||||
|
labels: chartProductLabels,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
legendWidget(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget legendWidget() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.circle, size: 10, color: Colors.cyan.shade300),
|
||||||
|
SizedBox(width: 2),
|
||||||
|
Text('Current Month', style: _styleSmall1),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
Icon(Icons.circle, size: 10, color: Colors.orange.shade300),
|
||||||
|
SizedBox(width: 2),
|
||||||
|
Text('Previous Month', style: _styleSmall1),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final _headerStyle2 = GoogleFonts.poppins(
|
final _headerStyle2 = GoogleFonts.poppins(
|
||||||
|
|||||||
177
lib/presentation/screens/dashboard/tabs/stacked_BAR_Exple.dart
Normal file
177
lib/presentation/screens/dashboard/tabs/stacked_BAR_Exple.dart
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../themes/charts/appChartColors.dart';
|
||||||
|
|
||||||
|
class BarChartSample4 extends StatefulWidget {
|
||||||
|
BarChartSample4({super.key});
|
||||||
|
|
||||||
|
final Color dark = Colors.blueAccent;
|
||||||
|
final Color normal = AppColors.contentColorCyan;
|
||||||
|
final Color light = Colors.limeAccent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => BarChartSample4State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class BarChartSample4State extends State<BarChartSample4> {
|
||||||
|
int rotationTurns = 1;
|
||||||
|
Widget bottomTitles(double value, TitleMeta meta) {
|
||||||
|
const style = TextStyle(fontSize: 10);
|
||||||
|
String text = switch (value.toInt()) {
|
||||||
|
0 => 'Apr',
|
||||||
|
1 => 'May',
|
||||||
|
2 => 'Jun',
|
||||||
|
3 => 'Jul',
|
||||||
|
4 => 'Aug',
|
||||||
|
_ => '',
|
||||||
|
};
|
||||||
|
return SideTitleWidget(
|
||||||
|
meta: meta,
|
||||||
|
child: Text(text, style: style),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget leftTitles(double value, TitleMeta meta) {
|
||||||
|
if (value == meta.max) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
const style = TextStyle(fontSize: 10);
|
||||||
|
return SideTitleWidget(
|
||||||
|
meta: meta,
|
||||||
|
child: Text(meta.formattedValue, style: style),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AspectRatio(
|
||||||
|
aspectRatio: 1.66,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 16),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final barsSpace = 4.0 * constraints.maxWidth / 400;
|
||||||
|
final barsWidth = 8.0 * constraints.maxWidth / 400;
|
||||||
|
return BarChart(
|
||||||
|
BarChartData(
|
||||||
|
alignment: BarChartAlignment.center,
|
||||||
|
rotationQuarterTurns: rotationTurns,
|
||||||
|
barTouchData: BarTouchData(enabled: false),
|
||||||
|
|
||||||
|
titlesData: FlTitlesData(
|
||||||
|
show: true,
|
||||||
|
bottomTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 28,
|
||||||
|
getTitlesWidget: bottomTitles,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leftTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 40,
|
||||||
|
getTitlesWidget: leftTitles,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
topTitles: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(showTitles: false),
|
||||||
|
),
|
||||||
|
rightTitles: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(showTitles: false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
gridData: FlGridData(
|
||||||
|
show: true,
|
||||||
|
checkToShowHorizontalLine: (value) => value % 10 == 0,
|
||||||
|
getDrawingHorizontalLine: (value) => FlLine(
|
||||||
|
color: AppColors.borderColor.withValues(alpha: 0.1),
|
||||||
|
strokeWidth: 1,
|
||||||
|
),
|
||||||
|
drawVerticalLine: false,
|
||||||
|
),
|
||||||
|
borderData: FlBorderData(show: false),
|
||||||
|
groupsSpace: barsSpace,
|
||||||
|
barGroups: getData(barsWidth),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BarChartGroupData> getData(double barsWidth) {
|
||||||
|
return [
|
||||||
|
/// -------------------- 1st Group --------------------
|
||||||
|
BarChartGroupData(
|
||||||
|
x: 0,
|
||||||
|
barRods: [
|
||||||
|
/// Last Month
|
||||||
|
BarChartRodData(
|
||||||
|
toY: 20000000000,
|
||||||
|
rodStackItems: [
|
||||||
|
BarChartRodStackItem(0, 3000000000, Colors.cyan),
|
||||||
|
BarChartRodStackItem(3000000000, 11000000000, Colors.blueAccent),
|
||||||
|
BarChartRodStackItem(11000000000, 20000000000, Colors.grey),
|
||||||
|
],
|
||||||
|
width: barsWidth,
|
||||||
|
borderRadius: BorderRadius.zero,
|
||||||
|
),
|
||||||
|
|
||||||
|
/// Current Month
|
||||||
|
BarChartRodData(
|
||||||
|
toY: 24000000000,
|
||||||
|
rodStackItems: [
|
||||||
|
BarChartRodStackItem(0, 2000000000, Colors.redAccent),
|
||||||
|
BarChartRodStackItem(2000000000, 11000000000, Colors.greenAccent),
|
||||||
|
BarChartRodStackItem(
|
||||||
|
11000000000,
|
||||||
|
24000000000,
|
||||||
|
Colors.purpleAccent,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
width: barsWidth,
|
||||||
|
borderRadius: BorderRadius.zero,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
/// -------------------- 2nd Group --------------------
|
||||||
|
BarChartGroupData(
|
||||||
|
x: 1,
|
||||||
|
barRods: [
|
||||||
|
/// Last Month
|
||||||
|
BarChartRodData(
|
||||||
|
toY: 28000000000,
|
||||||
|
rodStackItems: [
|
||||||
|
BarChartRodStackItem(0, 8000000000, Colors.cyan),
|
||||||
|
BarChartRodStackItem(8000000000, 22000000000, Colors.blueAccent),
|
||||||
|
BarChartRodStackItem(22000000000, 28000000000, Colors.grey),
|
||||||
|
],
|
||||||
|
width: barsWidth,
|
||||||
|
borderRadius: BorderRadius.zero,
|
||||||
|
),
|
||||||
|
|
||||||
|
/// Current Month
|
||||||
|
BarChartRodData(
|
||||||
|
toY: 34000000000,
|
||||||
|
rodStackItems: [
|
||||||
|
BarChartRodStackItem(0, 8000000000, Colors.redAccent),
|
||||||
|
BarChartRodStackItem(8000000000, 27000000000, Colors.greenAccent),
|
||||||
|
BarChartRodStackItem(
|
||||||
|
27000000000,
|
||||||
|
34000000000,
|
||||||
|
Colors.purpleAccent,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
width: barsWidth,
|
||||||
|
borderRadius: BorderRadius.zero,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -30,24 +30,7 @@ import '../../../../themes/indicators/text_field_theme_inline_editor.dart'
|
|||||||
hide UpperCaseTextFormatter;
|
hide UpperCaseTextFormatter;
|
||||||
|
|
||||||
class CreateProposal_Quick extends ConsumerStatefulWidget {
|
class CreateProposal_Quick extends ConsumerStatefulWidget {
|
||||||
// final dynamic userId;
|
const CreateProposal_Quick({super.key});
|
||||||
// final dynamic managerId;
|
|
||||||
// final dynamic selectedEnquiryId;
|
|
||||||
// final dynamic selectedInsurdId;
|
|
||||||
// final dynamic selectedQuotationFrmListdata;
|
|
||||||
// final dynamic selectedQuotationFrmListId;
|
|
||||||
// final void Function(String value) onSubmit;
|
|
||||||
|
|
||||||
const CreateProposal_Quick({
|
|
||||||
super.key,
|
|
||||||
// required this.userId,
|
|
||||||
// required this.managerId,
|
|
||||||
// required this.selectedEnquiryId,
|
|
||||||
// required this.selectedInsurdId,
|
|
||||||
// this.selectedQuotationFrmListdata,
|
|
||||||
// this.selectedQuotationFrmListId,
|
|
||||||
// required this.onSubmit,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<CreateProposal_Quick> createState() =>
|
ConsumerState<CreateProposal_Quick> createState() =>
|
||||||
|
|||||||
@ -3526,9 +3526,9 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
|
|||||||
// 'Proposal Rejected': 'Awaiting Payment',
|
// 'Proposal Rejected': 'Awaiting Payment',
|
||||||
// 'Policy Created': 'Policy Created',
|
// 'Policy Created': 'Policy Created',
|
||||||
// 'Policy Created': 'Policy Created',
|
// 'Policy Created': 'Policy Created',
|
||||||
'To be assigned': 'To be assigned',
|
'To be assigned': 'To Be Assigned',
|
||||||
'Assigned': 'Assigned',
|
'Assigned': 'Assigned',
|
||||||
'In progress': 'In progress',
|
'In progress': 'In Progress',
|
||||||
};
|
};
|
||||||
|
|
||||||
List<Map<String, dynamic>> get filteredDataByTab {
|
List<Map<String, dynamic>> get filteredDataByTab {
|
||||||
@ -3654,7 +3654,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
|
|||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
// '${config['icon']} $status',
|
// '${config['icon']} $status',
|
||||||
'$status',
|
_formatStatus(status),
|
||||||
style: GoogleFonts.inter(
|
style: GoogleFonts.inter(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
@ -4000,7 +4000,9 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: 60,
|
width: 60,
|
||||||
child: Text(
|
child: Text(
|
||||||
item['insurer_short_name'] ?? '-',
|
item['insurer_short_name'] != null
|
||||||
|
? item['insurer_short_name']
|
||||||
|
: '-',
|
||||||
style: _dataBoldthm2,
|
style: _dataBoldthm2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -4227,7 +4229,8 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
if (item['enquiry_status'] != 'Completed') ...[
|
if (item['enquiry_status'] != 'Completed' &&
|
||||||
|
roleId == 'manager') ...[
|
||||||
IconButton(
|
IconButton(
|
||||||
padding: EdgeInsets.zero, // remove outer padding
|
padding: EdgeInsets.zero, // remove outer padding
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
@ -5405,6 +5408,17 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
|
|||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
String _formatStatus(String status) {
|
||||||
|
return status
|
||||||
|
.split(' ')
|
||||||
|
.map(
|
||||||
|
(word) => word.isNotEmpty
|
||||||
|
? word[0].toUpperCase() + word.substring(1).toLowerCase()
|
||||||
|
: '',
|
||||||
|
)
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
String _truncateText(String text, int limit) {
|
String _truncateText(String text, int limit) {
|
||||||
if (text.length <= limit) return text;
|
if (text.length <= limit) return text;
|
||||||
return '${text.substring(0, limit)}...';
|
return '${text.substring(0, limit)}...';
|
||||||
|
|||||||
@ -15,7 +15,9 @@ import '../../../../data/utils/validators.dart';
|
|||||||
import '../../../providers/manager_provider.dart';
|
import '../../../providers/manager_provider.dart';
|
||||||
import '../../../providers/userRoleProvider.dart';
|
import '../../../providers/userRoleProvider.dart';
|
||||||
import '../../../themes/indicators/input_field_decoration.dart';
|
import '../../../themes/indicators/input_field_decoration.dart';
|
||||||
import '../../../themes/indicators/text_field_theme_inline_editor.dart';
|
import '../../../themes/indicators/text_field_theme.dart';
|
||||||
|
import '../../../themes/indicators/text_field_theme_inline_editor.dart'
|
||||||
|
hide UpperCaseTextFormatter;
|
||||||
|
|
||||||
class RaiseEnqForm extends ConsumerStatefulWidget {
|
class RaiseEnqForm extends ConsumerStatefulWidget {
|
||||||
final Function(Map<String, dynamic> data) onSubmit;
|
final Function(Map<String, dynamic> data) onSubmit;
|
||||||
@ -384,7 +386,7 @@ class RaiseEnqFormState extends ConsumerState<RaiseEnqForm> {
|
|||||||
// color: Color(0xFFFCFCFC),
|
// color: Color(0xFFFCFCFC),
|
||||||
// margin: const EdgeInsets.symmetric(vertical: 8),
|
// margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 25),
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 25),
|
||||||
height: MediaQuery.of(context).size.height * 0.16,
|
// height: MediaQuery.of(context).size.height * 0.16,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
// color: Colors.amber,
|
// color: Colors.amber,
|
||||||
@ -405,27 +407,30 @@ class RaiseEnqFormState extends ConsumerState<RaiseEnqForm> {
|
|||||||
child: FocusTraversalGroup(
|
child: FocusTraversalGroup(
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Row(
|
child: Container(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
// color: Colors.amber.shade100,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
child: Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
buildAgentName(context, fromHeader: true),
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
if (roleId != 'staff') ...[buildSelectStaffMem(context)],
|
children: [
|
||||||
|
buildAgentName(context, fromHeader: true),
|
||||||
|
if (roleId != 'staff') ...[buildSelectStaffMem(context)],
|
||||||
|
|
||||||
buildBroker(context),
|
buildBroker(context),
|
||||||
buildInsurer(context, fromHeader: true),
|
buildInsurer(context, fromHeader: true),
|
||||||
|
|
||||||
// buildInsuredName(context, fromHeader: true),
|
// buildInsuredName(context, fromHeader: true),
|
||||||
buildVehicleNumber(context),
|
buildVehicleNumber(context),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
buildRefresh(context),
|
buildRefresh(context),
|
||||||
SizedBox(width: 5),
|
SizedBox(width: 5),
|
||||||
buildSave(context),
|
buildSave(context),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -805,22 +810,31 @@ class RaiseEnqFormState extends ConsumerState<RaiseEnqForm> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildVehicleNumber(BuildContext context) {
|
Widget buildVehicleNumber(BuildContext context) {
|
||||||
return Column(
|
return SizedBox(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
width: MediaQuery.of(context).size.width * 0.15,
|
||||||
children: [
|
child: Column(
|
||||||
Text('Vehicle Number *', style: _subLabelTimeStyle),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Vehicle Number *', style: _subLabelTimeStyle),
|
||||||
|
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
SizedBox(
|
ThemedFormField(
|
||||||
width: MediaQuery.of(context).size.width * 0.15,
|
|
||||||
child: ThemedFormInlineField(
|
|
||||||
controller: controllers['regNo']!,
|
controller: controllers['regNo']!,
|
||||||
|
|
||||||
hintText: 'Vehicle Number',
|
|
||||||
borderColor: Color(0xFFE2E8F0),
|
borderColor: Color(0xFFE2E8F0),
|
||||||
highlightColor: Color(0xFF50A398),
|
highlightColor: Color(0xFF50A398),
|
||||||
padVertical: 13,
|
hintText: 'Vehicle Number',
|
||||||
|
txtwidth: MediaQuery.of(context).size.width * 0.26,
|
||||||
|
verticalPad: 15,
|
||||||
isdense: true,
|
isdense: true,
|
||||||
|
validator: (value) {
|
||||||
|
// Show backend validation message
|
||||||
|
if (fieldErrors['regNo'] != null &&
|
||||||
|
fieldErrors['regNo']!.isNotEmpty) {
|
||||||
|
return fieldErrors['regNo']; // show API message
|
||||||
|
}
|
||||||
|
|
||||||
|
return Validators.requiredField(value, "regNo");
|
||||||
|
},
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
UpperCaseTextFormatter(), // 👈 custom formatter for uppercase
|
UpperCaseTextFormatter(), // 👈 custom formatter for uppercase
|
||||||
FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9- ]')),
|
FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9- ]')),
|
||||||
@ -846,20 +860,54 @@ class RaiseEnqFormState extends ConsumerState<RaiseEnqForm> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
validator: (value) {
|
|
||||||
// Show backend validation message
|
|
||||||
if (fieldErrors['regNo'] != null &&
|
|
||||||
fieldErrors['regNo']!.isNotEmpty) {
|
|
||||||
return fieldErrors['regNo']; // show API message
|
|
||||||
}
|
|
||||||
|
|
||||||
return Validators.requiredField(value, "regNo");
|
|
||||||
},
|
|
||||||
|
|
||||||
widthNone: true,
|
|
||||||
),
|
),
|
||||||
),
|
// ThemedFormInlineField(
|
||||||
],
|
// controller: controllers['regNo']!,
|
||||||
|
//
|
||||||
|
// hintText: 'Vehicle Number',
|
||||||
|
// borderColor: Color(0xFFE2E8F0),
|
||||||
|
// highlightColor: Color(0xFF50A398),
|
||||||
|
// padVertical: 13,
|
||||||
|
// isdense: true,
|
||||||
|
// inputFormatters: [
|
||||||
|
// UpperCaseTextFormatter(), // 👈 custom formatter for uppercase
|
||||||
|
// FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9- ]')),
|
||||||
|
// ],
|
||||||
|
// // validator: (value) => Validators.requiredField(value, "regNo"),
|
||||||
|
// onChanged: (val) async {
|
||||||
|
// if (_isResetting) {
|
||||||
|
// print("Skipping API because _isResetting = true");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// print('Vehicle - $val');
|
||||||
|
// final res = await apiService.CheckDuplicate(context, val);
|
||||||
|
// print('Vehicle res- $res');
|
||||||
|
//
|
||||||
|
// if (res["message"] != null && res["message"] != "") {
|
||||||
|
// ToastHelper.showErrorToast(context, res['message']);
|
||||||
|
// setState(() {
|
||||||
|
// fieldErrors['regNo'] = res['message']; // store API message
|
||||||
|
// }); // 🔥 store backend message
|
||||||
|
// } else {
|
||||||
|
// setState(() {
|
||||||
|
// fieldErrors['regNo'] = res['message']; // store API message
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// validator: (value) {
|
||||||
|
// // Show backend validation message
|
||||||
|
// if (fieldErrors['regNo'] != null &&
|
||||||
|
// fieldErrors['regNo']!.isNotEmpty) {
|
||||||
|
// return fieldErrors['regNo']; // show API message
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return Validators.requiredField(value, "regNo");
|
||||||
|
// },
|
||||||
|
// // txtheight: 30,
|
||||||
|
// // txtwidth: MediaQuery.of(context).size.width * 0.01,
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -49,12 +49,13 @@ class _CustomBarChartState extends State<CustomBarChart> {
|
|||||||
return BarChartGroupData(
|
return BarChartGroupData(
|
||||||
x: x,
|
x: x,
|
||||||
groupVertically: false,
|
groupVertically: false,
|
||||||
|
// showingTooltipIndicators: [0],
|
||||||
// barsSpace: 20,
|
// barsSpace: 30,
|
||||||
barRods: [
|
barRods: [
|
||||||
BarChartRodData(toY: value1, color: Colors.cyan.shade300, width: 6),
|
BarChartRodData(toY: value1, color: Colors.cyan.shade300, width: 4),
|
||||||
|
|
||||||
// BarChartRodData(toY: 2, color: Colors.orange.shade300, width: 6),
|
// BarChartRodData(toY: 2, color: Colors.orange.shade300, width: 6),
|
||||||
BarChartRodData(toY: value2, color: Colors.orange.shade300, width: 6),
|
BarChartRodData(toY: value2, color: Colors.orange.shade300, width: 4),
|
||||||
],
|
],
|
||||||
showingTooltipIndicators: touchedGroupIndex == x ? [0] : [],
|
showingTooltipIndicators: touchedGroupIndex == x ? [0] : [],
|
||||||
);
|
);
|
||||||
@ -67,7 +68,72 @@ class _CustomBarChartState extends State<CustomBarChart> {
|
|||||||
child: BarChart(
|
child: BarChart(
|
||||||
BarChartData(
|
BarChartData(
|
||||||
alignment: BarChartAlignment.spaceBetween,
|
alignment: BarChartAlignment.spaceBetween,
|
||||||
|
// verticalAxis: Axis.horizontal,
|
||||||
rotationQuarterTurns: rotationTurns,
|
rotationQuarterTurns: rotationTurns,
|
||||||
|
|
||||||
|
// barTouchData: BarTouchData(
|
||||||
|
// // 1. Disable built-in touch handling
|
||||||
|
// handleBuiltInTouches: false,
|
||||||
|
// touchTooltipData: BarTouchTooltipData(
|
||||||
|
// // 3. Define the tooltip content
|
||||||
|
// getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||||
|
// // You can customize the content here
|
||||||
|
// return BarTooltipItem(
|
||||||
|
// '${rod.toY.round().toString()}', // Explicit value
|
||||||
|
// GoogleFonts.poppins(
|
||||||
|
// color: Colors.black,
|
||||||
|
// fontSize: 8,
|
||||||
|
// fontWeight: FontWeight.w500,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// // Customize appearance
|
||||||
|
// getTooltipColor: (BarChartGroupData group) => Colors.transparent,
|
||||||
|
// tooltipRoundedRadius: 8,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
barTouchData: BarTouchData(
|
||||||
|
enabled: true,
|
||||||
|
handleBuiltInTouches: true,
|
||||||
|
touchTooltipData: BarTouchTooltipData(
|
||||||
|
tooltipMargin: 6,
|
||||||
|
tooltipRoundedRadius: 6,
|
||||||
|
tooltipPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
// ask fl_chart to fit tooltip inside available space
|
||||||
|
fitInsideVertically: true, // if your fl_chart version supports it
|
||||||
|
fitInsideHorizontally:
|
||||||
|
true, // if your fl_chart version supports it
|
||||||
|
getTooltipItem:
|
||||||
|
(
|
||||||
|
BarChartGroupData group,
|
||||||
|
int groupIndex,
|
||||||
|
BarChartRodData rod,
|
||||||
|
int rodIndex,
|
||||||
|
) {
|
||||||
|
final textColor = rodIndex == 0
|
||||||
|
? Colors.cyan.shade300
|
||||||
|
: Colors.orange.shade300;
|
||||||
|
final textStyle = TextStyle(
|
||||||
|
color: textColor,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
);
|
||||||
|
return BarTooltipItem('${rod.toY.round()}', textStyle);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
touchCallback: (FlTouchEvent e, BarTouchResponse? r) {
|
||||||
|
// optional: change state to highlight tapped group
|
||||||
|
if (r == null || r.spot == null) {
|
||||||
|
setState(() => touchedGroupIndex = -1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() => touchedGroupIndex = r.spot!.touchedBarGroupIndex);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
borderData: FlBorderData(
|
borderData: FlBorderData(
|
||||||
show: true,
|
show: true,
|
||||||
border: Border(
|
border: Border(
|
||||||
@ -80,10 +146,14 @@ class _CustomBarChartState extends State<CustomBarChart> {
|
|||||||
),
|
),
|
||||||
gridData: FlGridData(
|
gridData: FlGridData(
|
||||||
show: true,
|
show: true,
|
||||||
|
// drawVerticalLine: true,
|
||||||
drawVerticalLine: false,
|
drawVerticalLine: false,
|
||||||
getDrawingHorizontalLine: (value) => FlLine(
|
getDrawingHorizontalLine: (value) => FlLine(
|
||||||
color: AppColors.borderColor.withValues(alpha: 0.2),
|
// color: AppColors.gridLinesColor.withValues(alpha: 0.2),
|
||||||
strokeWidth: 1,
|
color: Colors.blueGrey.shade100,
|
||||||
|
// color: Colors.black,
|
||||||
|
// color: AppColors.borderColor,
|
||||||
|
strokeWidth: 0.3,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
titlesData: FlTitlesData(
|
titlesData: FlTitlesData(
|
||||||
@ -112,8 +182,30 @@ class _CustomBarChartState extends State<CustomBarChart> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
rightTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
getTitlesWidget: (value, meta) {
|
||||||
|
return Transform.rotate(
|
||||||
|
angle: -3,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 3.0),
|
||||||
|
child: Text(
|
||||||
|
value.toInt().toString(),
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
style: GoogleFonts.inter(
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
reservedSize: 45,
|
||||||
|
),
|
||||||
|
),
|
||||||
leftTitles: const AxisTitles(),
|
leftTitles: const AxisTitles(),
|
||||||
// rightTitles: const AxisTitles(),
|
|
||||||
topTitles: const AxisTitles(),
|
topTitles: const AxisTitles(),
|
||||||
),
|
),
|
||||||
barGroups: widget.dataList.asMap().entries.map((e) {
|
barGroups: widget.dataList.asMap().entries.map((e) {
|
||||||
@ -134,49 +226,8 @@ class _CustomBarChartState extends State<CustomBarChart> {
|
|||||||
? widget.dataList!
|
? widget.dataList!
|
||||||
.map((e) => e.value1 > e.value2 ? e.value1 : e.value2)
|
.map((e) => e.value1 > e.value2 ? e.value1 : e.value2)
|
||||||
.reduce((a, b) => a > b ? a : b) +
|
.reduce((a, b) => a > b ? a : b) +
|
||||||
5
|
15
|
||||||
: 20,
|
: 20,
|
||||||
|
|
||||||
// barTouchData: BarTouchData(
|
|
||||||
// enabled: true,
|
|
||||||
// handleBuiltInTouches: false,
|
|
||||||
// touchTooltipData: BarTouchTooltipData(
|
|
||||||
// getTooltipColor: (group) => Colors.transparent,
|
|
||||||
// tooltipMargin: 0,
|
|
||||||
// getTooltipItem:
|
|
||||||
// (
|
|
||||||
// BarChartGroupData group,
|
|
||||||
// int groupIndex,
|
|
||||||
// BarChartRodData rod,
|
|
||||||
// int rodIndex,
|
|
||||||
// ) {
|
|
||||||
// return BarTooltipItem(
|
|
||||||
// rod.toY.toString(),
|
|
||||||
// TextStyle(
|
|
||||||
// fontWeight: FontWeight.bold,
|
|
||||||
// color: rod.color,
|
|
||||||
// fontSize: 18,
|
|
||||||
// shadows: const [
|
|
||||||
// Shadow(color: Colors.black26, blurRadius: 12),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// touchCallback: (event, response) {
|
|
||||||
// if (event.isInterestedForInteractions &&
|
|
||||||
// response != null &&
|
|
||||||
// response.spot != null) {
|
|
||||||
// setState(() {
|
|
||||||
// touchedGroupIndex = response.spot!.touchedBarGroupIndex;
|
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
// setState(() {
|
|
||||||
// touchedGroupIndex = -1;
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
165
lib/presentation/themes/charts/stackedBarChart.dart
Normal file
165
lib/presentation/themes/charts/stackedBarChart.dart
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
|
class CustomStackedBarChart extends StatefulWidget {
|
||||||
|
final List<String> labels;
|
||||||
|
final List<List<List<dynamic>>>
|
||||||
|
data; // data[group][stackIndex] = [start, end, color]
|
||||||
|
final double maxY;
|
||||||
|
final int initialRotation;
|
||||||
|
|
||||||
|
const CustomStackedBarChart({
|
||||||
|
super.key,
|
||||||
|
required this.labels,
|
||||||
|
required this.data,
|
||||||
|
this.maxY = 100.0,
|
||||||
|
this.initialRotation = 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CustomStackedBarChart> createState() => _CustomStackedBarChartState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomStackedBarChartState extends State<CustomStackedBarChart> {
|
||||||
|
late int rotationTurns;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
rotationTurns = widget.initialRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _toDouble(dynamic v) {
|
||||||
|
if (v == null) return 0.0;
|
||||||
|
if (v is double) return v;
|
||||||
|
if (v is int) return v.toDouble();
|
||||||
|
return double.tryParse(v.toString()) ?? 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
BarChartGroupData _generateGroup(int xIndex) {
|
||||||
|
// Each group is a single stacked rod (one staff)
|
||||||
|
final stacks = widget.data[xIndex];
|
||||||
|
final rodStackItems = <BarChartRodStackItem>[];
|
||||||
|
|
||||||
|
for (var s in stacks) {
|
||||||
|
// s expected: [start, end, color]
|
||||||
|
final start = _toDouble(s[0]);
|
||||||
|
final end = _toDouble(s[1]);
|
||||||
|
final color = (s.length > 2 && s[2] is Color)
|
||||||
|
? s[2] as Color
|
||||||
|
: Colors.grey;
|
||||||
|
rodStackItems.add(BarChartRodStackItem(start, end, color));
|
||||||
|
}
|
||||||
|
|
||||||
|
final maxEnd = rodStackItems.isNotEmpty
|
||||||
|
? rodStackItems.map((r) => r.toY).reduce((a, b) => a > b ? a : b)
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
|
return BarChartGroupData(
|
||||||
|
x: xIndex,
|
||||||
|
groupVertically: false,
|
||||||
|
barRods: [
|
||||||
|
BarChartRodData(
|
||||||
|
toY: maxEnd,
|
||||||
|
width: 12,
|
||||||
|
borderRadius: BorderRadius.zero,
|
||||||
|
rodStackItems: rodStackItems,
|
||||||
|
),
|
||||||
|
BarChartRodData(
|
||||||
|
toY: maxEnd,
|
||||||
|
width: 12,
|
||||||
|
borderRadius: BorderRadius.zero,
|
||||||
|
rodStackItems: rodStackItems,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final maxY = widget.maxY;
|
||||||
|
return AspectRatio(
|
||||||
|
aspectRatio: 1.6,
|
||||||
|
child: BarChart(
|
||||||
|
BarChartData(
|
||||||
|
alignment: BarChartAlignment.center,
|
||||||
|
rotationQuarterTurns: rotationTurns,
|
||||||
|
gridData: FlGridData(
|
||||||
|
show: true,
|
||||||
|
checkToShowHorizontalLine: (value) => value % (maxY / 5) == 0,
|
||||||
|
getDrawingHorizontalLine: (value) =>
|
||||||
|
FlLine(color: Colors.grey.withOpacity(0.12), strokeWidth: 0.8),
|
||||||
|
drawVerticalLine: false,
|
||||||
|
),
|
||||||
|
borderData: FlBorderData(show: false),
|
||||||
|
barTouchData: BarTouchData(
|
||||||
|
enabled: true,
|
||||||
|
touchTooltipData: BarTouchTooltipData(
|
||||||
|
tooltipRoundedRadius: 6,
|
||||||
|
tooltipPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
fitInsideHorizontally: true,
|
||||||
|
fitInsideVertically: true,
|
||||||
|
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||||
|
return BarTooltipItem(
|
||||||
|
'${rod.toY.toStringAsFixed(2)}',
|
||||||
|
GoogleFonts.poppins(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
titlesData: FlTitlesData(
|
||||||
|
bottomTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 56,
|
||||||
|
getTitlesWidget: (value, meta) {
|
||||||
|
final i = value.toInt();
|
||||||
|
if (i < 0 || i >= widget.labels.length)
|
||||||
|
return const SizedBox();
|
||||||
|
return SideTitleWidget(
|
||||||
|
meta: meta,
|
||||||
|
child: Text(
|
||||||
|
widget.labels[i],
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rightTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 40,
|
||||||
|
getTitlesWidget: (value, meta) {
|
||||||
|
return Text(
|
||||||
|
value.toInt().toString(),
|
||||||
|
style: GoogleFonts.inter(fontSize: 9),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leftTitles: const AxisTitles(),
|
||||||
|
topTitles: const AxisTitles(),
|
||||||
|
),
|
||||||
|
barGroups: List.generate(
|
||||||
|
widget.data.length,
|
||||||
|
(i) => _generateGroup(i),
|
||||||
|
),
|
||||||
|
maxY: maxY,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -202,6 +202,7 @@ class ThemedFormField extends HookWidget {
|
|||||||
fontWeight: FontWeight.w400, // optional
|
fontWeight: FontWeight.w400, // optional
|
||||||
color: textColr ?? Colors.black, // optional
|
color: textColr ?? Colors.black, // optional
|
||||||
),
|
),
|
||||||
|
onChanged: onChanged,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user