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/agentList.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/staffList.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(
|
||||
path: AppRoutes.enquiryLst,
|
||||
|
||||
@ -8,6 +8,8 @@ class AppRoutes {
|
||||
static const agent = '/agent/:id';
|
||||
static const String staffLst = '/staffLst';
|
||||
static const staff = '/staff/:id';
|
||||
static const String salesExecutiveLst = '/salesExecutiveLst';
|
||||
static const salesExecutive = '/salesExecutive/:id';
|
||||
static const String enquiryLst = '/enquiryLst';
|
||||
static const String enquiryForStaff = '/enquiryForStaff';
|
||||
static const String tabEnquiry = '/tabEnquiry';
|
||||
|
||||
@ -428,7 +428,9 @@ class ApiService {
|
||||
};
|
||||
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');
|
||||
} else {
|
||||
url = Uri.parse('${Env.apiUrl}/api/agent/changeAgentStatus');
|
||||
@ -557,6 +559,23 @@ class ApiService {
|
||||
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 ----------------------------------------------
|
||||
|
||||
Future<Map<String, dynamic>> fetchAgentIncentiveList(agentId) async {
|
||||
@ -705,6 +724,57 @@ class ApiService {
|
||||
final response = await _makeGetRequest(url, headers);
|
||||
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 -------------------------------------------------
|
||||
|
||||
|
||||
@ -56,7 +56,8 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
|
||||
} else if (currentRoute.contains('enquiry')) {
|
||||
_activeMenu = 'Enquiry';
|
||||
} else if (currentRoute.contains('agent') ||
|
||||
currentRoute.contains('staff')) {
|
||||
currentRoute.contains('staff') ||
|
||||
currentRoute.contains('salesExecutive')) {
|
||||
_activeMenu = 'User';
|
||||
} else if (currentRoute.contains('claim') ||
|
||||
currentRoute.contains('endosement') ||
|
||||
@ -145,6 +146,15 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
|
||||
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 (role == 'manager') ...[
|
||||
|
||||
@ -481,8 +481,11 @@ class _AddDialogState extends State<AddDialog> {
|
||||
onChanged: filterData,
|
||||
controller: _searchStaffController,
|
||||
txtwidth: ResponsiveLayout.isMobile(context)
|
||||
? MediaQuery.of(context).size.width * 0.7
|
||||
: MediaQuery.of(context).size.width * 0.15,
|
||||
? null
|
||||
: 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),
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
// import 'dart:io' as html;
|
||||
import 'dart:typed_data'; // Import for Uint8List
|
||||
import 'package:dropdown_search/dropdown_search.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -20,10 +21,13 @@ import '../../../../data/utils/Pagination.dart';
|
||||
import '../../../../data/utils/toastNotification.dart';
|
||||
import '../../../../data/utils/validators.dart';
|
||||
import '../../../layouts/main_layout.dart';
|
||||
import '../../../layouts/responsive_layout.dart';
|
||||
import '../../../providers/manager_provider.dart';
|
||||
import '../../../providers/userRoleProvider.dart';
|
||||
import '../../../providers/user_provider.dart';
|
||||
import '../../../themes/indicators/customizd_file_upload.dart';
|
||||
import '../../../themes/indicators/export_btn.dart';
|
||||
import '../../../themes/indicators/input_field_decoration.dart';
|
||||
import '../../../themes/indicators/search_field_theme.dart';
|
||||
import '../../../themes/indicators/singleFileUpload.dart';
|
||||
// import '../../../themes/indicators/upload_doc_theme.dart';
|
||||
@ -48,10 +52,19 @@ class AgentState extends ConsumerState<Agent> {
|
||||
String? selectedId;
|
||||
PlatformFile? docUploadedFile;
|
||||
|
||||
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKey =
|
||||
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
|
||||
|
||||
Map<String, TextEditingController> controllers = {};
|
||||
String? _token;
|
||||
dynamic userId;
|
||||
dynamic managerId;
|
||||
dynamic role;
|
||||
|
||||
bool isLoading = false;
|
||||
String? selectedSalesExectv;
|
||||
List<Map<String, dynamic>> filteredSalesExecutiveData = [];
|
||||
List<Map<String, dynamic>> getSalesExecutiveData = [];
|
||||
|
||||
Map<String, dynamic> dataDetails() {
|
||||
final data = {
|
||||
@ -60,6 +73,7 @@ class AgentState extends ConsumerState<Agent> {
|
||||
"mobile": controllers["mobile"]?.text,
|
||||
"address": controllers["address"]?.text,
|
||||
"agent_code": controllers["code"]?.text,
|
||||
'sales_executive_id': selectedSalesExectv,
|
||||
"is_active": isActive,
|
||||
"manager_id": managerId,
|
||||
};
|
||||
@ -79,6 +93,12 @@ class AgentState extends ConsumerState<Agent> {
|
||||
Future.microtask(() {
|
||||
managerId = ref.watch(managerIdProvider);
|
||||
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");
|
||||
}
|
||||
|
||||
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 {
|
||||
if (widget.id != null && widget.id != 'create') {
|
||||
dynamic response = await apiService.findSingleAgentData(widget.id!);
|
||||
@ -102,6 +157,7 @@ class AgentState extends ConsumerState<Agent> {
|
||||
controllers['code']?.text = data['agent_code'] ?? '';
|
||||
controllers['address']?.text = data['address'] ?? '';
|
||||
isActive = data["is_active"];
|
||||
selectedSalesExectv = data['sales_executive_id'] ?? '';
|
||||
|
||||
String? apiDocPath = data["certificate_file_name"];
|
||||
if (apiDocPath != null && apiDocPath.isNotEmpty) {
|
||||
@ -470,12 +526,19 @@ class AgentState extends ConsumerState<Agent> {
|
||||
SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: buildAddress()),
|
||||
Expanded(child: buildSalesExecutive(context)),
|
||||
SizedBox(width: 25),
|
||||
Expanded(child: buildUploadDocument()),
|
||||
Expanded(child: buildAddress()),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: buildUploadDocument()),
|
||||
SizedBox(width: 25),
|
||||
Expanded(child: SizedBox.shrink()),
|
||||
],
|
||||
),
|
||||
// Row(
|
||||
// children: [
|
||||
// Expanded(child: buildIncentiveFile()),
|
||||
@ -595,6 +658,157 @@ class AgentState extends ConsumerState<Agent> {
|
||||
borderColor: Color(0xFFE2E8F0),
|
||||
highlightColor: Color(0xFF50A398),
|
||||
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/partner.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 '../../../core/responsive/responsive_builder.dart';
|
||||
import '../../../core/routing/routes.dart';
|
||||
@ -135,7 +136,8 @@ class _DashboardState extends ConsumerState<Dashboard>
|
||||
LiveDashboard(),
|
||||
BussinessDashboard(),
|
||||
PartnerDashboard(),
|
||||
ProductiviyDashboard(),
|
||||
ProductivityDashboard(),
|
||||
// BarChartSample4(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -275,38 +275,69 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Hide and Show Charts : ', style: _styleSmall1),
|
||||
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() => showInsurer = !showInsurer);
|
||||
},
|
||||
child: Text(
|
||||
showInsurer ? "Insurer" : "Insurer Hidden",
|
||||
style: _styleSmall2,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Transform.scale(
|
||||
scale: 0.75,
|
||||
child: Checkbox(
|
||||
value: showInsurer,
|
||||
onChanged: (val) {
|
||||
setState(() => showInsurer = val!);
|
||||
},
|
||||
),
|
||||
),
|
||||
Text(
|
||||
// showInsurer ? "Insurer" : "Insurer Hidden",
|
||||
"Insurer",
|
||||
style: _styleSmall2,
|
||||
),
|
||||
],
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() => showBroker = !showBroker);
|
||||
},
|
||||
child: Text(
|
||||
showBroker ? "Broker" : "Broker Hidden",
|
||||
style: _styleSmall2,
|
||||
),
|
||||
|
||||
SizedBox(width: 10),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Transform.scale(
|
||||
scale: 0.75,
|
||||
child: Checkbox(
|
||||
value: showBroker,
|
||||
onChanged: (val) {
|
||||
setState(() => showBroker = val!);
|
||||
},
|
||||
),
|
||||
),
|
||||
Text(
|
||||
// showBroker ? "Broker" : "Broker Hidden",
|
||||
"Broker",
|
||||
style: _styleSmall2,
|
||||
),
|
||||
],
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() => showProduct = !showProduct);
|
||||
},
|
||||
child: Text(
|
||||
showProduct ? "Product " : "Product Hidden",
|
||||
style: _styleSmall2,
|
||||
),
|
||||
|
||||
SizedBox(width: 10),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Transform.scale(
|
||||
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(
|
||||
padding: EdgeInsets.all(10),
|
||||
margin: EdgeInsets.only(right: 20),
|
||||
clipBehavior: Clip.none,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
@ -364,9 +396,13 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: CustomBarChart(
|
||||
dataList: chartInsurerData,
|
||||
labels: chartInsurerLabels,
|
||||
child: Container(
|
||||
clipBehavior: Clip.none,
|
||||
// color: Colors.red.shade100,
|
||||
child: CustomBarChart(
|
||||
dataList: chartInsurerData,
|
||||
labels: chartInsurerLabels,
|
||||
),
|
||||
),
|
||||
),
|
||||
legendWidget(),
|
||||
|
||||
@ -9,52 +9,51 @@ import '../../../../core/services/api_service.dart';
|
||||
import '../../../../data/models/bar_data.dart';
|
||||
import '../../../providers/userRoleProvider.dart';
|
||||
import '../../../themes/charts/barChart.dart';
|
||||
import '../../../themes/charts/stackedBarChart.dart';
|
||||
|
||||
class ProductiviyDashboard extends ConsumerStatefulWidget {
|
||||
const ProductiviyDashboard({super.key});
|
||||
class ProductivityDashboard extends ConsumerStatefulWidget {
|
||||
const ProductivityDashboard({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ProductiviyDashboard> createState() =>
|
||||
_ProductiviyDashboardState();
|
||||
ConsumerState<ProductivityDashboard> createState() =>
|
||||
_ProductivityDashboardState();
|
||||
}
|
||||
|
||||
class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
||||
class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
|
||||
dynamic managerId;
|
||||
late ApiService apiService;
|
||||
|
||||
// List<Map<String, dynamic>> dataVal = [];
|
||||
List<Map<String, dynamic>> getBusinessDshBdData = [];
|
||||
List<Map<String, dynamic>> getProductivityDshBdData = [];
|
||||
List<Map<String, dynamic>> originalData = [];
|
||||
List<Map<String, dynamic>> filteredData = [];
|
||||
|
||||
List<Map<String, dynamic>> brokerWise = [];
|
||||
List<Map<String, dynamic>> productWise = [];
|
||||
List<Map<String, dynamic>> insurerWise = [];
|
||||
|
||||
List<Map<String, dynamic>> originalBrokerWise = [];
|
||||
List<Map<String, dynamic>> filteredBrokerWise = [];
|
||||
|
||||
List<Map<String, dynamic>> originalProductWise = [];
|
||||
List<Map<String, dynamic>> filteredProductWise = [];
|
||||
|
||||
List<Map<String, dynamic>> originalInsurerWise = [];
|
||||
List<Map<String, dynamic>> filteredInsurerWise = [];
|
||||
List<Map<String, dynamic>> originalStaffWise = [];
|
||||
List<Map<String, dynamic>> filteredStaffWise = [];
|
||||
|
||||
bool isLoading = false;
|
||||
bool isInsureWisePolicy = true;
|
||||
bool isBrokerWisePolicy = true;
|
||||
bool isProductWisePolicy = true;
|
||||
|
||||
bool showInsurer = true;
|
||||
bool showBroker = true;
|
||||
bool showStaff = true;
|
||||
bool showProduct = true;
|
||||
|
||||
List<String> staffLabels = [];
|
||||
List<List<List<dynamic>>> staffChartData = [];
|
||||
double maxPremium = 0;
|
||||
|
||||
int selectedView = 0;
|
||||
// 0 = Insurer
|
||||
// 0 = Staff
|
||||
// 1 = Broker
|
||||
// 2 = Product
|
||||
|
||||
String? SelctdBusinessbroker;
|
||||
String? SelctdProductivityproduct;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -70,54 +69,115 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
||||
// print("ENQroleId - $roleId");
|
||||
|
||||
if (managerId != null) {
|
||||
getBusinessDashBrdList(managerId);
|
||||
getProductivityDashBrdList(managerId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> getBusinessDashBrdList(id) async {
|
||||
Future<void> getProductivityDashBrdList(id) async {
|
||||
// print('getClaimList called MagId - $managerId - $role');
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
// dynamic id = widget.policyNumber;
|
||||
print('getBusinessDshID - $id');
|
||||
print('getProductivityDshID - $id');
|
||||
try {
|
||||
final response = await apiService.findBusinessDashboardData(
|
||||
final response = await apiService.findProductivityDashboardData(
|
||||
id,
|
||||
SelctdBusinessbroker,
|
||||
SelctdProductivityproduct,
|
||||
);
|
||||
|
||||
if (response['status'] == 'success') {
|
||||
final data = response['data'];
|
||||
print('getBusinessDshBdData - ${response['data']}');
|
||||
print('getProductivityDshBdData - ${response['data']}');
|
||||
setState(() {
|
||||
if (data is List) {
|
||||
// Already a list of maps
|
||||
getBusinessDshBdData = List<Map<String, dynamic>>.from(data);
|
||||
} else if (data is Map) {
|
||||
// Single object, wrap in a list
|
||||
getBusinessDshBdData = [Map<String, dynamic>.from(data)];
|
||||
} else {
|
||||
getBusinessDshBdData = [];
|
||||
if (data is Map<String, dynamic>) {
|
||||
staffLabels.clear();
|
||||
staffChartData.clear();
|
||||
maxPremium = 0;
|
||||
|
||||
final staffWise = (data['StaffWise'] ?? []) as List<dynamic>;
|
||||
|
||||
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;
|
||||
filteredData = List.from(originalData);
|
||||
// print('originalData - $getClaimPolicies');
|
||||
|
||||
// if (data is List) {
|
||||
// // Already a list of maps
|
||||
// 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>) {
|
||||
setState(() {
|
||||
// Store BrokerWise
|
||||
brokerWise = List<Map<String, dynamic>>.from(
|
||||
data['brokerWise'] ?? [],
|
||||
productWise = List<Map<String, dynamic>>.from(
|
||||
data['productWise'] ?? [],
|
||||
);
|
||||
|
||||
print('brokerWise - ${brokerWise}');
|
||||
originalBrokerWise = List.from(brokerWise);
|
||||
filteredBrokerWise = List.from(brokerWise);
|
||||
|
||||
// Store ProductWise
|
||||
productWise = List<Map<String, dynamic>>.from(
|
||||
data['productWise'] ?? [],
|
||||
@ -127,22 +187,22 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
||||
originalProductWise = List.from(productWise);
|
||||
filteredProductWise = List.from(productWise);
|
||||
|
||||
// Store InsurerWise
|
||||
// Store StaffWise
|
||||
insurerWise = List<Map<String, dynamic>>.from(
|
||||
data['insurerWise'] ?? [],
|
||||
);
|
||||
print('insurerWise - ${insurerWise}');
|
||||
originalInsurerWise = List.from(insurerWise);
|
||||
filteredInsurerWise = List.from(insurerWise);
|
||||
originalStaffWise = List.from(insurerWise);
|
||||
filteredStaffWise = List.from(insurerWise);
|
||||
});
|
||||
} else {
|
||||
// fallback
|
||||
brokerWise = [];
|
||||
productWise = [];
|
||||
productWise = [];
|
||||
insurerWise = [];
|
||||
}
|
||||
} else {
|
||||
getBusinessDshBdData = [];
|
||||
getProductivityDshBdData = [];
|
||||
originalData = [];
|
||||
}
|
||||
} catch (e) {
|
||||
@ -154,7 +214,7 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
||||
}
|
||||
}
|
||||
|
||||
List<BarData> buildInsurerBarData() {
|
||||
List<BarData> buildStaffBarData() {
|
||||
double parseDouble(dynamic value) {
|
||||
if (value == null) return 0.0;
|
||||
if (value is num) return value.toDouble();
|
||||
@ -162,7 +222,7 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return filteredInsurerWise.map((item) {
|
||||
return filteredStaffWise.map((item) {
|
||||
print('policies_current - ${item['total_policies_current_month']}');
|
||||
print('policies_PREV - ${item['total_policies_pre_month']}');
|
||||
|
||||
@ -181,8 +241,8 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<String> buildInsurerLabels() {
|
||||
return filteredInsurerWise.map<String>((item) {
|
||||
List<String> buildStaffLabels() {
|
||||
return filteredStaffWise.map<String>((item) {
|
||||
final name = item['short_name'];
|
||||
if (name != null && name.toString().trim().isNotEmpty) {
|
||||
return name.toString();
|
||||
@ -192,44 +252,6 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
||||
}).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() {
|
||||
double parseDouble(dynamic value) {
|
||||
if (value == null) return 0.0;
|
||||
@ -274,12 +296,196 @@ class _ProductiviyDashboardState extends ConsumerState<ProductiviyDashboard> {
|
||||
margin: EdgeInsets.only(left: 10, right: 10, bottom: 10),
|
||||
// color: Colors.amber.shade100,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [Text('No Data Found ', style: _styleSmall1)],
|
||||
children: [
|
||||
Row(
|
||||
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(
|
||||
|
||||
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;
|
||||
|
||||
class CreateProposal_Quick extends ConsumerStatefulWidget {
|
||||
// final dynamic userId;
|
||||
// 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,
|
||||
});
|
||||
const CreateProposal_Quick({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<CreateProposal_Quick> createState() =>
|
||||
|
||||
@ -3526,9 +3526,9 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
|
||||
// 'Proposal Rejected': 'Awaiting Payment',
|
||||
// 'Policy Created': 'Policy Created',
|
||||
// 'Policy Created': 'Policy Created',
|
||||
'To be assigned': 'To be assigned',
|
||||
'To be assigned': 'To Be Assigned',
|
||||
'Assigned': 'Assigned',
|
||||
'In progress': 'In progress',
|
||||
'In progress': 'In Progress',
|
||||
};
|
||||
|
||||
List<Map<String, dynamic>> get filteredDataByTab {
|
||||
@ -3654,7 +3654,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
// '${config['icon']} $status',
|
||||
'$status',
|
||||
_formatStatus(status),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 13,
|
||||
color: Colors.black,
|
||||
@ -4000,7 +4000,9 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
|
||||
SizedBox(
|
||||
width: 60,
|
||||
child: Text(
|
||||
item['insurer_short_name'] ?? '-',
|
||||
item['insurer_short_name'] != null
|
||||
? item['insurer_short_name']
|
||||
: '-',
|
||||
style: _dataBoldthm2,
|
||||
),
|
||||
),
|
||||
@ -4227,7 +4229,8 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
|
||||
),
|
||||
),
|
||||
|
||||
if (item['enquiry_status'] != 'Completed') ...[
|
||||
if (item['enquiry_status'] != 'Completed' &&
|
||||
roleId == 'manager') ...[
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero, // remove outer padding
|
||||
constraints: BoxConstraints(
|
||||
@ -5405,6 +5408,17 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
|
||||
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) {
|
||||
if (text.length <= limit) return text;
|
||||
return '${text.substring(0, limit)}...';
|
||||
|
||||
@ -15,7 +15,9 @@ import '../../../../data/utils/validators.dart';
|
||||
import '../../../providers/manager_provider.dart';
|
||||
import '../../../providers/userRoleProvider.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 {
|
||||
final Function(Map<String, dynamic> data) onSubmit;
|
||||
@ -384,7 +386,7 @@ class RaiseEnqFormState extends ConsumerState<RaiseEnqForm> {
|
||||
// color: Color(0xFFFCFCFC),
|
||||
// margin: const EdgeInsets.symmetric(vertical: 8),
|
||||
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(
|
||||
color: Colors.white,
|
||||
// color: Colors.amber,
|
||||
@ -405,27 +407,30 @@ class RaiseEnqFormState extends ConsumerState<RaiseEnqForm> {
|
||||
child: FocusTraversalGroup(
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
buildAgentName(context, fromHeader: true),
|
||||
if (roleId != 'staff') ...[buildSelectStaffMem(context)],
|
||||
child: Container(
|
||||
// color: Colors.amber.shade100,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
buildAgentName(context, fromHeader: true),
|
||||
if (roleId != 'staff') ...[buildSelectStaffMem(context)],
|
||||
|
||||
buildBroker(context),
|
||||
buildInsurer(context, fromHeader: true),
|
||||
buildBroker(context),
|
||||
buildInsurer(context, fromHeader: true),
|
||||
|
||||
// buildInsuredName(context, fromHeader: true),
|
||||
buildVehicleNumber(context),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
buildRefresh(context),
|
||||
SizedBox(width: 5),
|
||||
buildSave(context),
|
||||
],
|
||||
),
|
||||
],
|
||||
// buildInsuredName(context, fromHeader: true),
|
||||
buildVehicleNumber(context),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
buildRefresh(context),
|
||||
SizedBox(width: 5),
|
||||
buildSave(context),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -805,22 +810,31 @@ class RaiseEnqFormState extends ConsumerState<RaiseEnqForm> {
|
||||
}
|
||||
|
||||
Widget buildVehicleNumber(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Vehicle Number *', style: _subLabelTimeStyle),
|
||||
return SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.15,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Vehicle Number *', style: _subLabelTimeStyle),
|
||||
|
||||
SizedBox(height: 5),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.15,
|
||||
child: ThemedFormInlineField(
|
||||
SizedBox(height: 5),
|
||||
ThemedFormField(
|
||||
controller: controllers['regNo']!,
|
||||
|
||||
hintText: 'Vehicle Number',
|
||||
borderColor: Color(0xFFE2E8F0),
|
||||
highlightColor: Color(0xFF50A398),
|
||||
padVertical: 13,
|
||||
hintText: 'Vehicle Number',
|
||||
txtwidth: MediaQuery.of(context).size.width * 0.26,
|
||||
verticalPad: 15,
|
||||
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: [
|
||||
UpperCaseTextFormatter(), // 👈 custom formatter for uppercase
|
||||
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(
|
||||
x: x,
|
||||
groupVertically: false,
|
||||
|
||||
// barsSpace: 20,
|
||||
// showingTooltipIndicators: [0],
|
||||
// barsSpace: 30,
|
||||
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: value2, color: Colors.orange.shade300, width: 6),
|
||||
BarChartRodData(toY: value2, color: Colors.orange.shade300, width: 4),
|
||||
],
|
||||
showingTooltipIndicators: touchedGroupIndex == x ? [0] : [],
|
||||
);
|
||||
@ -67,7 +68,72 @@ class _CustomBarChartState extends State<CustomBarChart> {
|
||||
child: BarChart(
|
||||
BarChartData(
|
||||
alignment: BarChartAlignment.spaceBetween,
|
||||
// verticalAxis: Axis.horizontal,
|
||||
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(
|
||||
show: true,
|
||||
border: Border(
|
||||
@ -80,10 +146,14 @@ class _CustomBarChartState extends State<CustomBarChart> {
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
// drawVerticalLine: true,
|
||||
drawVerticalLine: false,
|
||||
getDrawingHorizontalLine: (value) => FlLine(
|
||||
color: AppColors.borderColor.withValues(alpha: 0.2),
|
||||
strokeWidth: 1,
|
||||
// color: AppColors.gridLinesColor.withValues(alpha: 0.2),
|
||||
color: Colors.blueGrey.shade100,
|
||||
// color: Colors.black,
|
||||
// color: AppColors.borderColor,
|
||||
strokeWidth: 0.3,
|
||||
),
|
||||
),
|
||||
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(),
|
||||
// rightTitles: const AxisTitles(),
|
||||
topTitles: const AxisTitles(),
|
||||
),
|
||||
barGroups: widget.dataList.asMap().entries.map((e) {
|
||||
@ -134,49 +226,8 @@ class _CustomBarChartState extends State<CustomBarChart> {
|
||||
? widget.dataList!
|
||||
.map((e) => e.value1 > e.value2 ? e.value1 : e.value2)
|
||||
.reduce((a, b) => a > b ? a : b) +
|
||||
5
|
||||
15
|
||||
: 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
|
||||
color: textColr ?? Colors.black, // optional
|
||||
),
|
||||
onChanged: onChanged,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user