// import 'dart:io' as html; // import 'dart:nativewrappers/_internal/vm/lib/typed_data_patch.dart'; import 'dart:convert'; import 'dart:typed_data'; // Import for Uint8List import 'package:dropdown_search/dropdown_search.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:nhance_partner/data/utils/toastNotification.dart'; import 'package:nhance_partner/presentation/themes/indicators/text_field_theme.dart'; import 'package:toastification/toastification.dart'; import 'package:universal_html/html.dart' as html; import '../../../../core/config/env.dart'; import '../../../../core/services/api_service.dart'; import '../../../../data/services/auth_service.dart'; import '../../../../data/utils/validators.dart'; import '../../../providers/manager_provider.dart'; import '../../../themes/indicators/customizd_file_upload.dart'; import '../../../themes/indicators/input_field_decoration.dart'; import '../../../themes/indicators/month_field_theme.dart'; import '../../../themes/indicators/search_field_theme.dart'; // import '../../../themes/indicators/upload_doc_theme.dart'; /// function to open the modal void showUploadIncentiveModal(BuildContext context) { showDialog( context: context, builder: (context) => const UploadIncentiveModal(), ); } class UploadIncentiveModal extends ConsumerStatefulWidget { const UploadIncentiveModal({super.key}); @override ConsumerState createState() => UploadIncentiveModalState(); } class UploadIncentiveModalState extends ConsumerState { late ApiService apiService; bool isLoading = false; final _formKey = GlobalKey(); final GlobalKey>> dropDownKey = GlobalKey>>(); List> getAgentData = []; List> filteredData = []; List> getAgentIncentiveFileData = []; List> filteredIncentiveData = []; Map controllers = {}; // html.File? passportFile; List selectedFiles = []; String? selectedFileNames; List tabHeader = ['name', 'agentId', 'date', 'search']; String? _token; dynamic userId; dynamic managerId; dynamic agentId; Map dataDetails() { final selectedDate = controllers["date"]?.text; final data = { "agent_id": agentId, "incentive_month": selectedDate, "created_by": userId, "file_type": "incentive", }; return data; } @override void initState() { super.initState(); apiService = ApiService(); for (String field in tabHeader) { controllers[field] = TextEditingController(); } Future.microtask(() { managerId = ref.watch(managerIdProvider); userId = ref.watch(userIdProvider); if (managerId != null) { getAgentList(managerId); } }); _initializeToken(); // getAgentList(); } Future _initializeToken() async { _token = await AuthService.getToken(); print("APISERTOKEN - $_token"); } @override void dispose() { for (var controller in controllers.values) { controller.dispose(); } controllers['date']?.dispose(); controllers['code']?.dispose(); super.dispose(); } Future refresh() async { setState(() { // reset file fields selectedFiles = []; // selectedFileNames = ""; selectedFileNames = null; // getAgentData = []; // filteredData = []; getAgentIncentiveFileData = []; filteredIncentiveData = []; // clear all text controllers for (var controller in controllers.values) { controller.clear(); } dropDownKey.currentState?.clear(); // 👈 clear selected agent controllers['agentId']?.clear(); // clear text field value too controllers['uploadFile']?.clear(); }); } void filterData(String query) { print("FilterDAta - $query"); setState(() { filteredIncentiveData = getAgentIncentiveFileData.where((item) { return (item['incentive_month'] ?? '-').toLowerCase().contains( query.toLowerCase(), ); }).toList(); }); } Future getAgentList(int id) async { print('getClaimList called'); setState(() { isLoading = true; }); try { final response = await apiService.fetchAgentUserList(managerId); if (response['status'] == 'success') { print('getAgentListData - ${response['data']}'); setState(() { getAgentData = List>.from(response['data']); print('API Data - $getAgentData'); filteredData = List.from(getAgentData); // print('originalData - $filteredData'); }); } else { getAgentData = []; filteredData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Future getAgentIncenctiveFileList(agentId) async { print('getClaimList agentId - $agentId'); setState(() { isLoading = true; }); try { final response = await apiService.fetchAgentIncentiveList(agentId, type: 'incentive'); if (response['status'] == 'success') { print('AgentIncentive - ${response['data']}'); setState(() { getAgentIncentiveFileData = List>.from( response['data'], ); print('API AgentIncentive - $getAgentIncentiveFileData'); filteredIncentiveData = List.from(getAgentIncentiveFileData); // print('originalData - $filteredIncentiveData'); }); } else { getAgentIncentiveFileData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Future handleSave() async { if (agentId == null) { ToastHelper.showErrorToast(context, 'Select Partner Name'); } else { if (!_formKey.currentState!.validate()) return; print("Handl1"); final dataSet = dataDetails(); print("dataSetAgent - $dataSet"); print("Handl13f"); if (selectedFiles.isEmpty) { print("❌ No file selected!"); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Please select a file before submitting"), ), ); return; } setState(() { uploadIncentiveData(dataSet); }); print("Handl2"); } } Future uploadIncentiveData(Map dataSet) async { final uri = Uri.parse('${Env.apiUrl}agent/uploadAgentIncentiveFile'); if (_token == null) { throw Exception('Token not found. Please log in.'); } // Use MultipartRequest (POST only) final request = http.MultipartRequest('POST', uri); request.headers['Authorization'] = 'Bearer $_token'; request.headers['app-signature'] = Env.App_Signature; print("USerDAta - $dataSet"); // dataSet.forEach((key, value) { // request.fields[key] = value.toString(); // print("✅ Encoded travel_details2: ${request.fields[key]}"); // }); dataSet.forEach((key, value) { if (key != 'incentive_file_name') { request.fields[key] = value.toString(); print("✅ Encoded $key: ${request.fields[key]}"); } }); if (selectedFiles.isNotEmpty) { for (final file in selectedFiles) { try { if (file.bytes != null) { final multipartFile = http.MultipartFile.fromBytes( 'incentive_file_name', file.bytes!, filename: file.name, ); request.files.add(multipartFile); } else if (file.path != null) { final multipartFile = await http.MultipartFile.fromPath( 'incentive_file_name', file.path!, filename: file.name, ); request.files.add(multipartFile); } print("📎 File attached: ${file.name}"); } catch (e) { print("❌ Failed to attach file ${file.name}: $e"); } } } // Attach file if selected // if (passportFile != null) { // try { // final reader = html.FileReader(); // reader.readAsArrayBuffer(passportFile! as html.Blob); // await reader.onLoad.first; // // final data = reader.result as Uint8List; // // final multipartFile = http.MultipartFile.fromBytes( // 'incentive_file_name', // data, // filename: passportFile!.name, // ); // // request.files.add(multipartFile); // print("📎 File attached: ${passportFile!.name}"); // } catch (e) { // print("❌ Failed to read file: $e"); // } // } else { // print("⚠️ No passport file selected."); // } print("🚀 Sending request with fields: ${request.fields}"); try { final streamedResponse = await request.send(); final response = await http.Response.fromStream(streamedResponse); print("Response status: ${response.statusCode}"); print("Response body: ${response.body}"); final responseBody = json.decode(response.body); if (response.statusCode == 200 || response.statusCode == 201) { // dispose(); print("✅ Partner submitted successfully!"); print("📨 Response: ${response.body}"); final data = dataDetails(); final agentId = data["agent_id"]; final status = responseBody['status']; final message = responseBody['data'] ?? ''; if (status == 'success') { ToastHelper.showSuccessToast(context, 'File uploaded successfully!'); getAgentIncenctiveFileList(agentId); } else { ToastHelper.showErrorToast( context, message.isNotEmpty ? message : 'Something went wrong!', ); } // context.go(AppRoutes.agentLst); } else if (response.statusCode == 403) { await apiService.clearLocalStorageAndRedirect(); } else { print("❌ Submission failed. Status: ${response.statusCode}"); print("📨 Body: ${response.body}"); String errorMessage = 'There was a problem in creating user. Please try again.'; try { if (responseBody['data'] is String && (responseBody['data'] as String).isNotEmpty) { final parsedData = json.decode(responseBody['data']); if (parsedData is Map && parsedData['message'] != null) { errorMessage = parsedData['message'].toString(); } } else if (responseBody['message'] != null) { errorMessage = responseBody['message'].toString(); } } catch (_) { // keep fallback message } ToastHelper.showErrorToast(context, errorMessage); } } catch (e) { print("🔥 Error submitting user: $e"); } } @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; final screenHeight = MediaQuery.of(context).size.height; final isCompact = screenWidth < 1000; final dialogWidth = isCompact ? screenWidth * 0.95 : screenWidth * 0.55; final dialogHeight = isCompact ? screenHeight * 0.82 : screenHeight * 0.7; final fieldWidth = isCompact ? dialogWidth * 0.9 : screenWidth * 0.27; return SelectionArea( child:AlertDialog( backgroundColor: Colors.white, content: Container( width: dialogWidth, height: dialogHeight, child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( "Incentive Files", style: GoogleFonts.poppins( fontSize: 14, color: Color(0xFF50A398), fontWeight: FontWeight.w500, ), ), Spacer(), Tooltip( message: 'Refresh', child: IconButton( icon: const Icon( Icons.refresh, size: 18, color: Color(0xFF2E7D6E), // color: Color(0xFF425B5B), ), onPressed: () { refresh(); }, splashRadius: 28, hoverColor: Colors.black12, padding: const EdgeInsets.all(8), constraints: const BoxConstraints(), ), ), Tooltip( message: 'Close', child: IconButton( icon: const Icon( Icons.close, size: 18, // color: Color(0xFF425B5B), color: Color(0xFF2E7D6E), ), onPressed: () { Navigator.pop(context); }, splashRadius: 28, hoverColor: Colors.black12, padding: const EdgeInsets.all(8), constraints: const BoxConstraints(), ), ), ], ), const SizedBox(height: 5), isCompact ? Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Partner Name', style: _labelStyle), SizedBox(height: 5), Container( width: fieldWidth, height: 40, child: DropdownSearch>( key: dropDownKey, selectedItem: null, items: (filter, infiniteScrollProps) { return filteredData; // pass the whole object }, itemAsString: (agent) => agent['name'].toString(), // what to show compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Partner", ).copyWith( hintStyle: GoogleFonts.inter( fontSize: 12, color: Colors.black, ), filled: true, fillColor: Colors.white, enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Color(0xFFE2E8F0), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Color(0xFFE2E8F0), width: 1.5, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 6, ), ), ), popupProps: PopupProps.menu( fit: FlexFit.loose, menuProps: MenuProps(backgroundColor: Colors.white), showSearchBox: true, searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search partner...", hintStyle: GoogleFonts.inter( fontSize: 12, color: Colors.black, ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.white, ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Color(0xFFEDF6F5), width: 1, ), ), ), ), itemBuilder: ( context, item, isDisabled, isSelected, ) { return Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3, ), child: Text( item['name'].toString(), style: GoogleFonts.inter( fontSize: 12, color: Colors.black, ), ), ); }, ), onChanged: (agent) { if (agent != null) { print("Selected Partner Name: ${agent['name']}"); print("Agent Code: ${agent['agent_code']}"); print("Agent Id: ${agent['id']}"); getAgentIncenctiveFileList(agent['id']); controllers['agentId']?.text = agent['agent_code']; agentId = agent['id']; } }, ), ), ], ), SizedBox(height: 10), Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Partner ID', style: _labelStyle), SizedBox(height: 5), ThemedFormField( controller: controllers['agentId']!, txtwidth: fieldWidth, txtheight: 40, borderColor: Color(0xFFE2E8F0), highlightColor: Color(0xFF50A398), readOnly: true, ), ], ), ], ) : Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Partner Name', style: _labelStyle), SizedBox(height: 5), Container( width: fieldWidth, height: 40, child: DropdownSearch>( key: dropDownKey, selectedItem: null, items: (filter, infiniteScrollProps) { return filteredData; // pass the whole object }, // itemAsString: (agent) => agent['name'], // items: (filter, infiniteScrollProps) { // return getAgentData // .map((agent) => agent['name'].toString()) // .toList(); // }, itemAsString: (agent) => agent['name'].toString(), // what to show compareFn: (item, selectedItem) => item['id'] == selectedItem['id'], // ✅ compare by id decoratorProps: DropDownDecoratorProps( decoration: AppInputDecorations.dropdownDecoration( label: "Select Partner", ).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: PopupProps.menu( fit: FlexFit.loose, menuProps: MenuProps(backgroundColor: Colors.white), showSearchBox: true, searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search partner...", 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, ), ), ), ), // constraints: BoxConstraints(), itemBuilder: (context, item, isDisabled, isSelected) { return Container( // color: isSelected ? Colors.blue.withOpacity(0.1) : null, padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3, ), child: Text( item['name'].toString(), style: GoogleFonts.inter( fontSize: 12, color: Colors.black, ), ), ); }, ), onChanged: (agent) { if (agent != null) { print("Selected Partner Name: ${agent['name']}"); print("Agent Code: ${agent['agent_code']}"); print("Agent Id: ${agent['id']}"); getAgentIncenctiveFileList(agent['id']); controllers['agentId']?.text = agent['agent_code']; agentId = agent['id']; } }, ), ), ], ), SizedBox(width: 10), Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Partner ID', style: _labelStyle), SizedBox(height: 5), ThemedFormField( controller: controllers['agentId']!, txtwidth: fieldWidth, txtheight: 40, borderColor: Color(0xFFE2E8F0), highlightColor: Color(0xFF50A398), // backgroundColor: Color(0xFFECECEC), readOnly: true, // backgroundColor: Color(0xFFCBCBCB), ), ], ), ], ), const SizedBox(height: 20), Text("Upload Documents", style: _labelHeaderStyle), const SizedBox(height: 15), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Form( key: _formKey, child: Row( children: [ Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Month', style: _labelStyle, ), SizedBox(height: 5), ThemedMonthField( hintText: "Select Month", txtwidth: MediaQuery.of(context).size.width * 0.27, txtheight: 40, validator: (value) => Validators.requiredField(value, "date"), borderColor: Color(0xFFE2E8F0), highlightColor: Color(0xFF50A398), controller: controllers['date']!, onDateSelected: (date) { print("Picked Date: $date"); }, ), ], ), SizedBox(width: 15), Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text('Upload Files', style: _labelStyle), ], ), SizedBox(height: 5), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ ThemedUploadField( key: ValueKey(selectedFileNames), allowMultiple: true, // hintText: "Upload Document", txtheight: 40, // validator: (value) => Validators.requiredField( // value, // "Upload Document", // ), // validator: (value) { // if (passportFile == null) { // return "Please upload a file"; // } // return null; // }, // backgroundColor: Color(0xFFECECEC), backgroundColor: Colors.white, borderColor: Color(0xFFE2E8F0), allowedExtensions: null, hintText: (selectedFileNames == null || selectedFileNames!.isEmpty) ? "Upload Document" : selectedFileNames, txtwidth: isCompact ? fieldWidth * 0.71 : MediaQuery.of(context).size.width * 0.19, onFilesSelected: (fileNames, files) { print("Files picked: ${fileNames.join(', ')}"); // print("Size: ${file.size}"); // print( // "Path: ${file.path}", // ); // works on mobile/desktop // print("Bytes: ${file.bytes}"); setState(() { selectedFiles = files; selectedFileNames = fileNames.join(', '); }); }, ), SizedBox(width: 15), Container( width: isCompact ? fieldWidth * 0.25 : MediaQuery.of(context).size.width * 0.068, child: ElevatedButton( style: ElevatedButton.styleFrom( // backgroundColor: Color(0xFF425B5B), backgroundColor: Color(0xFF2E7D6E), foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 6, ), // 👈 reduce radius (default ~20) ), ), onPressed: () { handleSave(); // Navigator.pop(context); }, child: Text( "Submit", style: GoogleFonts.poppins(fontSize: 12), ), ), ), ], ), ], ), ], ), ), ], ), const SizedBox(height: 15), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Files', style: _labelHeaderStyle), ThemedSearchField( hintText: 'Search', backgroundColor: Colors.white, // backgroundColor: Color(0xFFECECEC), onChanged: filterData, // onChanged: null, controller: controllers['search']!, txtwidth: MediaQuery.of(context).size.width * 0.15, txtHeight: 30, ), ], ), const SizedBox(height: 10), Expanded( child: isLoading ? const Center(child: CircularProgressIndicator()) : filteredIncentiveData.isEmpty ? const Center(child: Text("No files found")) : LayoutBuilder( builder: (context, constraints) { final fileAreaWidth = constraints.maxWidth; final crossAxisCount = fileAreaWidth < 520 ? 1 : 2; const spacing = 10.0; final tileWidth = (fileAreaWidth - spacing * (crossAxisCount - 1) - 16) / crossAxisCount; const targetTileHeight = 70.0; final aspectRatio = (tileWidth / targetTileHeight) .clamp(3.2, 12.0); return GridView.builder( padding: const EdgeInsets.all(8), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, crossAxisSpacing: spacing, mainAxisSpacing: spacing, childAspectRatio: aspectRatio, ), itemCount: filteredIncentiveData.length, itemBuilder: (context, index) { final file = filteredIncentiveData[index]; return IncentiveFileRow( fileName: file['incentive_file_name'] ?? "Unknown", date: file['incentive_month'] ?? "-", onUpload: () { print("Upload ${file['id']}"); final selectedId = file['id']; final path = 'agent/downloadAgentIncentiveFile?id=$selectedId'; apiService.getPdfDownload(path, selectedId); }, onDelete: () async { print("Delete ${file['id']}"); final selectedId = file['id']; final response = await apiService .deleteAgentIncentiveFile(selectedId); print("res - $response"); if (response['status'] == 'success') { print("DELTED"); final data = dataDetails(); // Map final agentId = data["agent_id"]; getAgentIncenctiveFileList(agentId); } else { // Fluttertoast.showToast( // msg: "Something went wrong", // toastLength: Toast.LENGTH_SHORT, // gravity: ToastGravity.BOTTOM, // ); } }, ); }, ); }, ), ), ], ), ), ),); } } final _labelStyle = GoogleFonts.poppins( color: Colors.black, fontWeight: FontWeight.w400, fontSize: 12, ); final _labelHeaderStyle = GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, ); class IncentiveFileRow extends StatelessWidget { final String fileName; final String date; final VoidCallback? onUpload; final VoidCallback? onDelete; const IncentiveFileRow({ super.key, required this.fileName, required this.date, this.onUpload, this.onDelete, }); @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), decoration: BoxDecoration( border: Border.all(color: const Color(0xFFE3E3E3)), borderRadius: BorderRadius.circular(6), ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(6.0), decoration: BoxDecoration( color: const Color(0xFFF4F6F8), borderRadius: BorderRadius.circular(6.0), border: Border.all(color: const Color(0xFFE3E3E3)), ), child: const Icon( Icons.file_present, color: Color(0xFF838587), size: 18, ), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( fileName, style: GoogleFonts.poppins( fontWeight: FontWeight.w400, fontSize: 12, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), const SizedBox(height: 2), Text( date, style: GoogleFonts.poppins( color: const Color(0xFF6E6E6E), fontSize: 10, ), ), ], ), ), const SizedBox(width: 8), Material( color: Colors.transparent, child: InkWell( onTap: onUpload, borderRadius: BorderRadius.circular(20), child: const Padding( padding: EdgeInsets.all(6), child: Icon( Icons.file_download_outlined, color: Color(0xFF6E6E6E), size: 20, ), ), ), ), Material( color: Colors.transparent, child: InkWell( onTap: onDelete, borderRadius: BorderRadius.circular(20), child: const Padding( padding: EdgeInsets.all(6), child: Icon( Icons.delete_outlined, color: Color(0xFF6E6E6E), size: 20, ), ), ), ), ], ), ); } }