// 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 'package:nhance_partner/core/config/env.dart'; import 'package:nhance_partner/core/services/api_service.dart'; import 'package:nhance_partner/data/services/auth_service.dart'; import 'package:nhance_partner/data/utils/validators.dart'; import 'package:nhance_partner/presentation/layouts/main_layout.dart'; import 'package:nhance_partner/presentation/providers/manager_provider.dart'; import 'package:nhance_partner/presentation/themes/indicators/customizd_file_upload.dart'; import 'package:nhance_partner/presentation/themes/indicators/input_field_decoration.dart'; import 'package:nhance_partner/presentation/themes/indicators/month_field_theme.dart'; import 'package:nhance_partner/presentation/themes/indicators/search_field_theme.dart'; // import '../../../themes/indicators/upload_doc_theme.dart'; /// function to open the modal void showUploadGridModal(BuildContext context) { showDialog( context: context, builder: (context) => const UploadGridModal(), ); } class UploadGridModal extends ConsumerStatefulWidget { const UploadGridModal({super.key}); @override ConsumerState createState() => UploadGridModalState(); } class UploadGridModalState extends ConsumerState { late ApiService apiService; bool isLoading = false; final _formKey = GlobalKey(); final GlobalKey>> dropDownKey = GlobalKey>>(); List> getAgentData = []; List> filteredData = []; List> getAgentGridFileData = []; List> filteredGridData = []; Map controllers = {}; // html.File? passportFile; List selectedFiles = []; String? selectedFileNames; List tabHeader = ['name', 'agentId', 'date', 'search']; String? _token; dynamic userId; dynamic managerId; dynamic agentId; DateTime? _validFrom; String _formatDateForApi(DateTime date) => date.toIso8601String().split('T').first; String _formatDateForUi(DateTime date) { const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', ]; return '${date.day} ${months[date.month - 1]} ${date.year}'; } Future _pickValidFrom() async { final today = DateTime.now(); final minDate = DateTime(today.year, today.month, today.day); final safeInitial = (_validFrom != null && _validFrom!.isAfter(minDate)) ? _validFrom! : minDate; final picked = await showDatePicker( context: context, firstDate: minDate, lastDate: DateTime(2100), initialDate: safeInitial, ); if (picked == null) return; setState(() => _validFrom = picked); // Backend expects `incentive_month` but payout-grid upload uses a full date. controllers['date']?.text = _formatDateForApi(picked); } Map dataDetails() { final selectedDate = controllers["date"]?.text; final data = { "agent_id": agentId, "incentive_month": selectedDate, "created_by": userId, "file_type": "grid", }; return data; } @override void initState() { super.initState(); apiService = ApiService(); for (String field in tabHeader) { controllers[field] = TextEditingController(); } Future.microtask(_loadInitialModalData); _initializeToken(); // getAgentList(); } Future _loadInitialModalData() async { managerId = ref.read(managerIdProvider); userId = ref.read(userIdProvider); agentId = null; if (managerId != null) { await getAgentList(managerId); } // Always fetch history list when modal opens. // if (agentId != null) { // await getAgentIncenctiveFileList(agentId); // } } 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 = []; getAgentGridFileData = []; filteredGridData = []; // clear all text controllers for (var controller in controllers.values) { controller.clear(); } _validFrom = null; dropDownKey.currentState?.clear(); // 👈 clear selected agent controllers['agentId']?.clear(); // clear text field value too controllers['uploadFile']?.clear(); }); // Re-fetch history after clearing UI fields. // if (agentId != null) { // await getAgentIncenctiveFileList(agentId); // } } void filterData(String query) { print("FilterDAta - $query"); setState(() { filteredGridData = getAgentGridFileData.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: 'grid'); if (response['status'] == 'success') { print('AgentIncentive - ${response['data']}'); setState(() { getAgentGridFileData = List>.from( response['data'], ); print('API AgentIncentive - $getAgentGridFileData'); filteredGridData = List.from(getAgentGridFileData); // print('originalData - $filteredGridData'); }); } else { getAgentGridFileData = []; } } catch (e) { print('Exception occurred: $e'); } finally { setState(() { isLoading = false; }); } } Future handleSave() async { final dateText = controllers['date']?.text.trim() ?? ''; if (dateText.isEmpty) { ToastHelper.showErrorToast(context, 'Select valid from date'); return; } 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; } final invalidFiles = selectedFiles.where((file) { final name = file.name.toLowerCase(); return !(name.endsWith('.xls') || name.endsWith('.xlsx')); }).toList(); if (invalidFiles.isNotEmpty) { ToastHelper.showErrorToast( context, 'Upload only Excel files (.xls, .xlsx)', ); return; } setState(() { uploadIncentiveData(dataSet); }); print("Handl2"); } Future uploadIncentiveData(Map dataSet) async { final uri = Uri.parse('${Env.apiUrl}grid/upload'); 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 = null; final status = responseBody['status']; final message = responseBody['data'] ?? ''; if (status == 'success') { ToastHelper.showSuccessToast(context, 'File uploaded successfully!'); // await getAgentIncenctiveFileList(agentId); // Close the modal and signal the parent to refresh. Navigator.pop(context, true); } 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 fieldWidth = isCompact ? dialogWidth * 0.9 : screenWidth * 0.27; return SelectionArea( child:AlertDialog( backgroundColor: Colors.white, content: Container( width: dialogWidth, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( "Payout 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: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Form( key: _formKey, child: Row( children: [ Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Valid From', style: _labelStyle, ), SizedBox(height: 5), SizedBox( width: MediaQuery.of(context).size.width * 0.27, height: 40, child: InkWell( onTap: _pickValidFrom, borderRadius: BorderRadius.circular(6), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), border: Border.all(color: const Color(0xFFE2E8F0)), color: Colors.white, ), child: Row( children: [ Expanded( child: Text( _validFrom == null ? 'Select Valid From' : _formatDateForUi(_validFrom!), style: GoogleFonts.inter( fontSize: 12, color: _validFrom == null ? const Color(0xFFCBD5E1) : Colors.black, ), ), ), const Icon( Icons.calendar_today_outlined, size: 16, color: Color(0xFF94A3B8), ), ], ), ), ), ), ], ), 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: const ['xls', 'xlsx'], 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), // fILE LIST section // 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()) // : filteredGridData.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: filteredGridData.length, // itemBuilder: (context, index) { // final file = filteredGridData[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?type=grid&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 = null; // // // 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, ), ), ), ), ], ), ); } } class GridUploadScreen extends StatelessWidget { const GridUploadScreen({super.key}); @override Widget build(BuildContext context) { return MainLayout( title: 'Payout Upload', body: Center( child: ElevatedButton.icon( onPressed: () => showUploadGridModal(context), icon: const Icon(Icons.upload_file_outlined), label: const Text('Open Payout Upload'), ), ), ); } }