EnQList added policy detls
This commit is contained in:
parent
53a86a8e21
commit
485894ba98
File diff suppressed because one or more lines are too long
@ -27,6 +27,7 @@ import '../../presentation/screens/home/home_screen.dart';
|
||||
import '../../presentation/screens/login/login_screen.dart';
|
||||
import '../../presentation/screens/payout/payout_screen.dart';
|
||||
import '../../presentation/screens/splash/splash_screen.dart';
|
||||
import '../../presentation/screens/staff/Enquiry/enquiry_inline_Edit_17Nov.dart';
|
||||
import '../../presentation/screens/staff/Enquiry/enquiry_inline_list.dart';
|
||||
import '../../presentation/screens/staff/Enquiry/enquiry_list.dart';
|
||||
import '../../presentation/screens/staff/policy/policy.dart';
|
||||
@ -178,6 +179,8 @@ final GoRouter appRouter = GoRouter(
|
||||
GoRoute(
|
||||
path: AppRoutes.enquiryForStaff,
|
||||
builder: (context, state) => const EnquiryListStaffInline(),
|
||||
// builder: (context, state) => const EnquiryListStaffInlineTST(),
|
||||
|
||||
// builder: (context, state) => const EnquiryStaff(),
|
||||
),
|
||||
GoRoute(
|
||||
|
||||
@ -91,6 +91,44 @@ class ApiService {
|
||||
}
|
||||
// --------------------------- COMMON FILES __________________________________________
|
||||
|
||||
Future<Map<String, dynamic>> createUserData(data, path) async {
|
||||
print('path - $path');
|
||||
final url = Uri.parse('${Env.apiUrl}$path');
|
||||
|
||||
if (_token == null) {
|
||||
await _initializeToken();
|
||||
}
|
||||
|
||||
print("data------- $data}");
|
||||
|
||||
final headers = {
|
||||
'Authorization': 'Bearer $_token' ?? '',
|
||||
'Content-Type': 'application/json',
|
||||
'app-signature': Env.App_Signature,
|
||||
};
|
||||
|
||||
final response = await _makePostRequestJson(url, data, headers);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> fetchUploadEnquiryFiles(dynamic enqId) async {
|
||||
// print(_token);
|
||||
if (_token == null) {
|
||||
await _initializeToken();
|
||||
}
|
||||
|
||||
final url = Uri.parse(
|
||||
'${Env.apiUrl}enquiry/enquiryFiles?enquiry_id=$enqId',
|
||||
);
|
||||
|
||||
final headers = {
|
||||
'Authorization': 'Bearer $_token' ?? '',
|
||||
'app-signature': Env.App_Signature,
|
||||
};
|
||||
final response = await _makeGetRequest(url, headers);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> logoutUsingAPI(agentId) async {
|
||||
// print(_token);
|
||||
if (_token == null) {
|
||||
@ -762,6 +800,23 @@ class ApiService {
|
||||
|
||||
//------------------------------------ Quotation ------------------------------------------------
|
||||
|
||||
Future<Map<String, dynamic>> fetchQuickQuotationList(int managerId) async {
|
||||
// print(_token);
|
||||
if (_token == null) {
|
||||
await _initializeToken();
|
||||
}
|
||||
|
||||
final url = Uri.parse(
|
||||
'${Env.apiUrl}enquiry/getQuickQuoteEnquiryList?manager_id=${managerId}',
|
||||
);
|
||||
final headers = {
|
||||
'Authorization': 'Bearer $_token' ?? '',
|
||||
'app-signature': Env.App_Signature,
|
||||
};
|
||||
final response = await _makeGetRequest(url, headers);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> fetchQuotationList(int managerId) async {
|
||||
// print(_token);
|
||||
if (_token == null) {
|
||||
@ -1212,7 +1267,6 @@ class ApiService {
|
||||
|
||||
// --------------------------------- PayOut Module----------------------------------------------
|
||||
Future<Map<String, dynamic>> getCommissionRateList(data) async {
|
||||
|
||||
final url = Uri.parse('${Env.apiUrl}/invoice/commission-rate-list');
|
||||
|
||||
// final token = await getToken(); // Fetch token
|
||||
@ -1235,7 +1289,6 @@ class ApiService {
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getCreateOrUpdate(data) async {
|
||||
|
||||
final url = Uri.parse('${Env.apiUrl}/invoice/create-or-update');
|
||||
|
||||
// final token = await getToken(); // Fetch token
|
||||
@ -1289,6 +1342,22 @@ class ApiService {
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> delete(ID, path) async {
|
||||
// print(_token);
|
||||
if (_token == null) {
|
||||
await _initializeToken();
|
||||
}
|
||||
|
||||
final url = Uri.parse('${Env.apiUrl}$path');
|
||||
|
||||
final headers = {
|
||||
'Authorization': 'Bearer $_token' ?? '',
|
||||
'app-signature': Env.App_Signature,
|
||||
};
|
||||
final response = await _makeGetRequest(url, headers);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getInvoiceDetails(ID) async {
|
||||
// print(_token);
|
||||
if (_token == null) {
|
||||
|
||||
@ -11,6 +11,8 @@ import '../../data/services/auth_service.dart';
|
||||
import '../providers/manager_provider.dart';
|
||||
import '../providers/userRoleProvider.dart';
|
||||
import '../screens/UserManagement/Profile/profile_web.dart';
|
||||
import '../screens/staff/Enquiry/Proposal_QuickCreation/Proposal_QuickCreation.dart';
|
||||
import '../themes/indicators/side_Drawer_Panel.dart';
|
||||
|
||||
class AppHeader extends ConsumerStatefulWidget implements PreferredSizeWidget {
|
||||
const AppHeader({super.key});
|
||||
@ -380,7 +382,11 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
debugPrint("Notifications tapped");
|
||||
SideDrawerPanel.show(
|
||||
context: context,
|
||||
title: 'Proposal Creation',
|
||||
child: CreateProposal_Quick(),
|
||||
);
|
||||
},
|
||||
tooltip: 'Create Proposal',
|
||||
),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1469
lib/presentation/screens/staff/Enquiry/Proposal_QuickCreation/re
Normal file
1469
lib/presentation/screens/staff/Enquiry/Proposal_QuickCreation/re
Normal file
File diff suppressed because it is too large
Load Diff
@ -39,13 +39,13 @@ import '../../../themes/indicators/text_field_theme_inline_editor.dart'
|
||||
import '../../../widgets/custom_Stdate_EnDate_Filter.dart';
|
||||
import '../assignStaff.dart';
|
||||
|
||||
class EnquiryListStaffInline extends ConsumerStatefulWidget {
|
||||
const EnquiryListStaffInline({super.key});
|
||||
class EnquiryListStaffInlineTST extends ConsumerStatefulWidget {
|
||||
const EnquiryListStaffInlineTST({super.key});
|
||||
@override
|
||||
ConsumerState<EnquiryListStaffInline> createState() => EnquiryStaffState();
|
||||
ConsumerState<EnquiryListStaffInlineTST> createState() => EnquiryStaffState();
|
||||
}
|
||||
|
||||
class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
|
||||
class EnquiryStaffState extends ConsumerState<EnquiryListStaffInlineTST> {
|
||||
int currentPage = 1;
|
||||
int itemsPerPage = 10;
|
||||
late ApiService apiService;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
293
lib/presentation/screens/staff/Enquiry/multi_file_list.dart
Normal file
293
lib/presentation/screens/staff/Enquiry/multi_file_list.dart
Normal file
@ -0,0 +1,293 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/src/widgets/framework.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/presentation/themes/indicators/mutli_FileUpload.dart';
|
||||
|
||||
import '../../../../core/config/env.dart';
|
||||
import '../../../../core/services/api_service.dart';
|
||||
import '../../../../data/services/auth_service.dart';
|
||||
import '../../../../data/utils/toastNotification.dart';
|
||||
import '../../../themes/indicators/multi_fileUploadService.dart';
|
||||
|
||||
class MultipleFileWidget extends ConsumerStatefulWidget {
|
||||
dynamic enqId;
|
||||
MultipleFileWidget({super.key, required this.enqId});
|
||||
|
||||
@override
|
||||
ConsumerState<MultipleFileWidget> createState() => MultipleFileWidgetState();
|
||||
}
|
||||
|
||||
class MultipleFileWidgetState extends ConsumerState<MultipleFileWidget> {
|
||||
List<PlatformFile> uploadedFiles = [];
|
||||
final fileService = MultiFileUploadService();
|
||||
late ApiService apiService;
|
||||
bool isLoading = false;
|
||||
dynamic _token;
|
||||
|
||||
List<Map<String, dynamic>> getUploadedData = [];
|
||||
List<Map<String, dynamic>> originalData = [];
|
||||
List<Map<String, dynamic>> filteredData = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
fileService.clearAll();
|
||||
apiService = ApiService();
|
||||
_initializeToken();
|
||||
getList();
|
||||
}
|
||||
|
||||
Future<void> _initializeToken() async {
|
||||
_token = await AuthService.getToken();
|
||||
print("APISERTOKEN - $_token");
|
||||
}
|
||||
|
||||
Future<void> getList() async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final response = await apiService.fetchUploadEnquiryFiles(widget.enqId);
|
||||
|
||||
if (response['status'] == 'success') {
|
||||
print('E113 => getfetchUploadEnquiryFiles=');
|
||||
print('E113 => getfetchUploadEnquiryFiles=> ${response['data']}');
|
||||
setState(() {
|
||||
getUploadedData = List<Map<String, dynamic>>.from(response['data']);
|
||||
originalData = getUploadedData;
|
||||
filteredData = List.from(originalData);
|
||||
print('originalData - $originalData');
|
||||
});
|
||||
} else {
|
||||
getUploadedData = [];
|
||||
originalData = [];
|
||||
}
|
||||
} catch (e) {
|
||||
print('Exception occurred: $e');
|
||||
} finally {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> handleSave() async {
|
||||
final request = http.MultipartRequest(
|
||||
'POST',
|
||||
Uri.parse('${Env.apiUrl}enquiry/uploadEnquiryFiles'),
|
||||
);
|
||||
|
||||
if (_token == null) {
|
||||
throw Exception('Token not found. Please log in.');
|
||||
}
|
||||
request.headers['Authorization'] = 'Bearer $_token';
|
||||
request.headers['app-signature'] = Env.App_Signature;
|
||||
if (MultiFileUploadService().files.isEmpty) {
|
||||
setState(() {
|
||||
MultiFileUploadWidget.hasFiles = false;
|
||||
});
|
||||
ToastHelper.showErrorToast(
|
||||
context,
|
||||
'Please upload at least one document',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// final id = int.tryParse(enqId.toString()) ?? 0;
|
||||
|
||||
// get uploaded files
|
||||
final uploadedFiles = MultiFileUploadService().files;
|
||||
print("📁 Total uploaded files: ${uploadedFiles.length}");
|
||||
print(
|
||||
"📂 File list (original names): ${uploadedFiles.map((f) => f.file.name).toList()}",
|
||||
);
|
||||
|
||||
// Validate all labels present (optional but recommended)
|
||||
for (final uf in uploadedFiles) {
|
||||
print(
|
||||
"📝 Checking label for file: ${uf.file.name}, current label: '${uf.label}'",
|
||||
);
|
||||
if ((uf.label ?? '').trim().isEmpty) {
|
||||
ToastHelper.showErrorToast(
|
||||
context,
|
||||
'Please enter name for all uploaded documents',
|
||||
);
|
||||
setState(() => isLoading = false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add files
|
||||
for (var uf in uploadedFiles) {
|
||||
final pf = uf.file;
|
||||
if (pf.bytes != null) {
|
||||
request.files.add(
|
||||
http.MultipartFile.fromBytes('files[]', pf.bytes!, filename: pf.name),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Combine all names into a JSON array string
|
||||
final claimDocNames = uploadedFiles.map((uf) => uf.label.trim()).toList();
|
||||
final encodedNames = jsonEncode(claimDocNames);
|
||||
request.fields['files_doc_names'] = encodedNames;
|
||||
request.fields['enquiry_id'] = widget.enqId!;
|
||||
// ✅ Debug print
|
||||
print("Payload being sent:");
|
||||
print("Files: ${uploadedFiles.map((f) => f.file.name).toList()}");
|
||||
print("Names (JSON): $encodedNames");
|
||||
|
||||
final response = await request.send();
|
||||
final responseBody = await response.stream.bytesToString();
|
||||
final decoded = jsonDecode(responseBody);
|
||||
if (decoded['status'] == 'success') {
|
||||
ToastHelper.showSuccessToast(context, 'File Uploaded');
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
print('Form data submitted successfully');
|
||||
fileService.clearAll();
|
||||
getList();
|
||||
} else {
|
||||
ToastHelper.showErrorToast(context, "Failed: ${decoded['message']}");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> handleDelete(id) async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
|
||||
final path = 'enquiry/deleteEnquiryFile?file_id=$id';
|
||||
print('path - $path');
|
||||
try {
|
||||
final response = await apiService.delete(id, path);
|
||||
|
||||
print("response");
|
||||
print("response - $response");
|
||||
print("response - ${response['status']}");
|
||||
|
||||
if (response['status'] == 'success') {
|
||||
ToastHelper.showSuccessToast(context, response['message']);
|
||||
getList();
|
||||
} else {
|
||||
ToastHelper.showSuccessToast(context, response['message']);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Exception occurred: $e');
|
||||
} finally {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Color(0xFFFFFFFF),
|
||||
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('File Upload', style: GoogleFonts.poppins(fontSize: 11)),
|
||||
SizedBox(height: 3),
|
||||
MultiFileUploadWidget(),
|
||||
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
handleSave();
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 2),
|
||||
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
color: Color(0xFF2E7D6E),
|
||||
// color: Color(0xFF425B5B),
|
||||
),
|
||||
child: Text(
|
||||
'Save',
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: 5),
|
||||
Column(
|
||||
children: List.generate(originalData.length, (index) {
|
||||
final item = originalData[index];
|
||||
final fileName = item['file_name'] ?? '';
|
||||
final fileId = item['id'] ?? '';
|
||||
|
||||
return Container(
|
||||
// margin: const EdgeInsets.only(bottom: 6),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
width: 0.2,
|
||||
color: Colors.blueGrey.shade100,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.file_present_outlined,
|
||||
size: 16,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
fileName,
|
||||
style: GoogleFonts.poppins(fontSize: 11),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
final path =
|
||||
'api/enquiry/downloadEnquiryFiles?file_id=$fileId';
|
||||
print("Uploadpath $path");
|
||||
apiService.getPdfDownload(path, fileId);
|
||||
},
|
||||
child: Icon(Icons.download, size: 16, color: Colors.blue),
|
||||
),
|
||||
|
||||
// DELETE BUTTON (optional)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
handleDelete(fileId);
|
||||
},
|
||||
child: Icon(Icons.delete, size: 16, color: Colors.red),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -483,6 +483,11 @@ class RaiseEnqFormState extends ConsumerState<RaiseEnqForm> {
|
||||
label: "Partner Name",
|
||||
).copyWith(
|
||||
filled: true,
|
||||
// errorStyle: const TextStyle(
|
||||
// fontSize: 9,
|
||||
//
|
||||
// // height: 0.0
|
||||
// ),
|
||||
fillColor:
|
||||
Colors.white, // 👈 makes the dropdown input white
|
||||
isDense: true,
|
||||
|
||||
@ -503,38 +503,6 @@ class CreateProposalFormState extends ConsumerState<CreateProposalForm> {
|
||||
height: MediaQuery.of(context).size.height * 0.525,
|
||||
child: buildFormFields(context),
|
||||
),
|
||||
|
||||
// Row(
|
||||
// crossAxisAlignment: CrossAxisAlignment.end,
|
||||
// mainAxisAlignment: MainAxisAlignment.end,
|
||||
// children: [
|
||||
// GestureDetector(
|
||||
// onTap: () {
|
||||
// handleDone();
|
||||
// // widget.onSubmit(controller.text.trim());
|
||||
// // Navigator.of(context).pop();
|
||||
// },
|
||||
// child: Container(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// horizontal: 25.0,
|
||||
// vertical: 10,
|
||||
// ),
|
||||
// decoration: BoxDecoration(
|
||||
// borderRadius: BorderRadius.circular(8.0),
|
||||
// color: const Color(0xFF425B5B),
|
||||
// ),
|
||||
// child: Text(
|
||||
// 'Save',
|
||||
// style: GoogleFonts.poppins(
|
||||
// color: Colors.white,
|
||||
// fontSize: 12,
|
||||
// fontWeight: FontWeight.w600,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ class ThemedUploadField extends StatefulWidget {
|
||||
final String? hintText;
|
||||
final double? txtwidth;
|
||||
final double? txtheight;
|
||||
final double? fontSZ;
|
||||
final double? borderCirculr;
|
||||
final Color? backgroundColor;
|
||||
final Color? borderColor;
|
||||
final Color? errorBorderColor;
|
||||
@ -23,6 +25,8 @@ class ThemedUploadField extends StatefulWidget {
|
||||
super.key,
|
||||
this.hintText,
|
||||
this.txtwidth,
|
||||
this.borderCirculr,
|
||||
this.fontSZ,
|
||||
this.txtheight,
|
||||
this.backgroundColor,
|
||||
this.borderColor,
|
||||
@ -89,7 +93,9 @@ class _ThemedUploadFieldState extends State<ThemedUploadField> {
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor ?? Colors.white,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: BorderRadius.circular(
|
||||
widget.borderCirculr ?? 10,
|
||||
),
|
||||
border: Border.all(
|
||||
color: errorMessage != null
|
||||
? (widget.errorBorderColor ?? Colors.red)
|
||||
@ -108,7 +114,7 @@ class _ThemedUploadFieldState extends State<ThemedUploadField> {
|
||||
widget.hintText ??
|
||||
"Upload Document",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontSize: widget.fontSZ ?? 12,
|
||||
color: errorMessage != null
|
||||
? Colors.red
|
||||
: Colors.black,
|
||||
@ -119,6 +125,7 @@ class _ThemedUploadFieldState extends State<ThemedUploadField> {
|
||||
const Icon(
|
||||
Icons.file_upload_outlined,
|
||||
color: Colors.black,
|
||||
size: 12,
|
||||
),
|
||||
],
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ class _ThemedMultiFileUploadFieldState
|
||||
Future<void> _pickFile() async {
|
||||
final error = await fileService.pickFiles(
|
||||
maxFileSizeInMB: 3,
|
||||
allowedExtensions: widget.allowedExtensions,
|
||||
// allowedExtensions: widget.allowedExtensions,
|
||||
);
|
||||
|
||||
if (error != null) {
|
||||
@ -60,6 +60,9 @@ class _ThemedMultiFileUploadFieldState
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(error)));
|
||||
} else if (fileService.pickFiles != null) {
|
||||
print('Files Picked ');
|
||||
print('Files Picked fileService - ${fileService.pickFiles}');
|
||||
|
||||
// setState(() {
|
||||
// selectedFileName = fileService.pickFiles!.name;
|
||||
// errorMessage = null;
|
||||
|
||||
@ -1,51 +1,71 @@
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class UploadedFile {
|
||||
final PlatformFile file;
|
||||
final TextEditingController controller;
|
||||
UploadedFile({required this.file})
|
||||
: controller = TextEditingController(
|
||||
text: file.extension != null
|
||||
? file.name.replaceAll('.${file.extension}', '')
|
||||
: file.name,
|
||||
);
|
||||
|
||||
String get label => controller.text;
|
||||
void dispose() => controller.dispose();
|
||||
}
|
||||
|
||||
class MultiFileUploadService {
|
||||
MultiFileUploadService._();
|
||||
static final MultiFileUploadService _instance = MultiFileUploadService._();
|
||||
factory MultiFileUploadService() => _instance;
|
||||
|
||||
final List<PlatformFile> _files = [];
|
||||
final List<UploadedFile> _files = [];
|
||||
|
||||
List<PlatformFile> get files => List.unmodifiable(_files);
|
||||
// Expose as unmodifiable but containing UploadedFile objects
|
||||
List<UploadedFile> get files => List.unmodifiable(_files);
|
||||
|
||||
// Allowed extensions
|
||||
// static const allowedExtensions = ['pdf', 'png', 'jpg', 'jpeg', 'heic'];
|
||||
static const defaultAllowedExtensions = ['pdf', 'png', 'jpg', 'jpeg'];
|
||||
static const allowedExtensions = ['pdf', 'png', 'jpg', 'jpeg', 'heic'];
|
||||
|
||||
Future<String?> pickFiles({
|
||||
int maxFileSizeInMB = 3,
|
||||
List<String>? allowedExtensions,
|
||||
}) async {
|
||||
final extensions = allowedExtensions ?? defaultAllowedExtensions;
|
||||
Future<String?> pickFiles({int maxFileSizeInMB = 3}) async {
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
allowMultiple: true,
|
||||
withData: true,
|
||||
type: FileType.custom,
|
||||
allowedExtensions: extensions,
|
||||
allowedExtensions: allowedExtensions,
|
||||
);
|
||||
|
||||
if (result != null) {
|
||||
for (final file in result.files) {
|
||||
final ext = file.extension?.toLowerCase() ?? '';
|
||||
final sizeInMB = (file.size / (1024 * 1024));
|
||||
for (final pf in result.files) {
|
||||
final ext = pf.extension?.toLowerCase() ?? '';
|
||||
final sizeInMB = (pf.size / (1024 * 1024));
|
||||
|
||||
if (!allowedExtensions!.contains(ext)) {
|
||||
return "Unsupported file format: ${file.name}";
|
||||
if (!allowedExtensions.contains(ext)) {
|
||||
return "Unsupported file format: ${pf.name}";
|
||||
}
|
||||
if (sizeInMB > maxFileSizeInMB) {
|
||||
return "File too large (${file.name}). Max $maxFileSizeInMB MB allowed.";
|
||||
return "File too large (${pf.name}). Max $maxFileSizeInMB MB allowed.";
|
||||
}
|
||||
}
|
||||
|
||||
_files.addAll(result.files);
|
||||
// Wrap PlatformFile in our UploadedFile model
|
||||
_files.add(UploadedFile(file: pf));
|
||||
}
|
||||
}
|
||||
return null; // success
|
||||
return null;
|
||||
}
|
||||
|
||||
void removeFileAt(int index) {
|
||||
if (index >= 0 && index < _files.length) _files.removeAt(index);
|
||||
if (index >= 0 && index < _files.length) {
|
||||
_files[index].dispose();
|
||||
_files.removeAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
void clearAll() => _files.clear();
|
||||
void clearAll() {
|
||||
for (final f in _files) {
|
||||
f.dispose();
|
||||
}
|
||||
_files.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import '../../layouts/responsive_layout.dart';
|
||||
import 'multi_fileUploadService.dart';
|
||||
|
||||
class MultiFileUploadWidget extends StatefulWidget {
|
||||
@ -6,6 +9,8 @@ class MultiFileUploadWidget extends StatefulWidget {
|
||||
|
||||
@override
|
||||
State<MultiFileUploadWidget> createState() => _MultiFileUploadWidgetState();
|
||||
|
||||
static bool hasFiles = false; // ✅ Static variable to check from parent
|
||||
}
|
||||
|
||||
class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
|
||||
@ -40,13 +45,16 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
|
||||
} else {
|
||||
setState(() {
|
||||
errorMessage = null;
|
||||
MultiFileUploadWidget.hasFiles = fileService.files.isNotEmpty;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _removeFile(int index) {
|
||||
fileService.removeFileAt(index);
|
||||
setState(() {});
|
||||
setState(() {
|
||||
MultiFileUploadWidget.hasFiles = fileService.files.isNotEmpty;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@ -56,44 +64,117 @@ class _MultiFileUploadWidgetState extends State<MultiFileUploadWidget> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// if (ResponsiveLayout.isMobile(context)) ...[
|
||||
OutlinedButton.icon(
|
||||
onPressed: _pickFiles,
|
||||
icon: const Icon(
|
||||
Icons.file_upload_outlined,
|
||||
color: Color(0xFFE26728),
|
||||
size: 24,
|
||||
color: Color(0xFF2E7D6E),
|
||||
size: 18,
|
||||
),
|
||||
label: const Text(
|
||||
"Upload Documents",
|
||||
style: TextStyle(fontSize: 14, color: Colors.black),
|
||||
style: TextStyle(fontSize: 11, color: Colors.black),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: const BorderSide(color: Color(0xFFE26728)),
|
||||
side: const BorderSide(color: Color(0xFF2E7D6E)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
"Supports only PDF, PNG, JPG, JPEG,HEIC formats (max 10 MB each)",
|
||||
style: GoogleFonts.poppins(fontSize: 8, color: Colors.grey),
|
||||
),
|
||||
// ]
|
||||
// else ...[
|
||||
// Row(
|
||||
// children: [
|
||||
// OutlinedButton.icon(
|
||||
// onPressed: _pickFiles,
|
||||
// icon: const Icon(
|
||||
// Icons.file_upload_outlined,
|
||||
// color: Color(0xFFE26728),
|
||||
// size: 24,
|
||||
// ),
|
||||
// label: const Text(
|
||||
// "Upload Documents",
|
||||
// style: TextStyle(fontSize: 14, color: Colors.black),
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// style: OutlinedButton.styleFrom(
|
||||
// side: const BorderSide(color: Color(0xFFE26728)),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 12),
|
||||
// // const Expanded(
|
||||
// // child: Text(
|
||||
// // "Supports only PDF, PNG, JPG, JPEG,HEIC formats (max 10 MB each)",
|
||||
// // style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
// // overflow: TextOverflow.ellipsis,
|
||||
// // ),
|
||||
// // ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
|
||||
// if (fileService.files.isEmpty && errorMessage == null) ...[
|
||||
// const SizedBox(height: 4),
|
||||
// const Text(
|
||||
// "Required",
|
||||
// style: TextStyle(color: Colors.red, fontSize: 12),
|
||||
// ),
|
||||
// ],
|
||||
if (errorMessage != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
errorMessage!,
|
||||
style: const TextStyle(color: Colors.red, fontSize: 12),
|
||||
style: GoogleFonts.poppins(color: Colors.red, fontSize: 12),
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 8),
|
||||
...files.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final file = entry.value;
|
||||
|
||||
return ListTile(
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(file.name, style: const TextStyle(fontSize: 14)),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.close, color: Colors.red),
|
||||
onPressed: () => _removeFile(index),
|
||||
),
|
||||
...fileService.files.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final uploaded = entry.value; // UploadedFile
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
uploaded.file.name,
|
||||
style: GoogleFonts.poppins(fontSize: 11),
|
||||
),
|
||||
trailing: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
splashRadius: 2,
|
||||
icon: const Icon(Icons.close, color: Colors.red, size: 14),
|
||||
onPressed: () => _removeFile(index),
|
||||
),
|
||||
),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(
|
||||
// left: 8.0,
|
||||
// bottom: 8.0,
|
||||
// right: 8.0,
|
||||
// ),
|
||||
// child: TextField(
|
||||
// controller: uploaded.controller,
|
||||
// style: GoogleFonts.poppins(fontSize: 11),
|
||||
// decoration: InputDecoration(
|
||||
// labelText: 'Enter document name',
|
||||
// labelStyle: GoogleFonts.poppins(fontSize: 11),
|
||||
//
|
||||
// border: const OutlineInputBorder(),
|
||||
// isDense: true,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
|
||||
121
lib/presentation/themes/indicators/side_Drawer_Panel.dart
Normal file
121
lib/presentation/themes/indicators/side_Drawer_Panel.dart
Normal file
@ -0,0 +1,121 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
/// A reusable side drawer that slides in from the right
|
||||
/// Takes up 1/3 of the screen width
|
||||
class SideDrawerPanel extends StatelessWidget {
|
||||
final Widget child;
|
||||
final String? title;
|
||||
final VoidCallback? onClose;
|
||||
final double? width;
|
||||
|
||||
const SideDrawerPanel({
|
||||
Key? key,
|
||||
required this.child,
|
||||
this.title,
|
||||
this.onClose,
|
||||
this.width,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final drawerWidth = width ?? screenWidth / 2;
|
||||
|
||||
return Container(
|
||||
width: drawerWidth,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
spreadRadius: 2,
|
||||
offset: const Offset(-2, 0),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
// color: const Color(0xFFF8F9FF),
|
||||
border: Border(bottom: BorderSide(color: Colors.grey.shade200)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (title != null)
|
||||
Text(
|
||||
'Proposal Creation',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xFF1E293B),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: onClose ?? () => Navigator.of(context).pop(),
|
||||
color: Colors.grey.shade600,
|
||||
tooltip: 'Close',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Content
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
// child: child,
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
elevation: 0,
|
||||
child: child, // Your CreateProposal_Quick()
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Show the drawer as a modal overlay
|
||||
static Future<T?> show<T>({
|
||||
required BuildContext context,
|
||||
required Widget child,
|
||||
String? title,
|
||||
double? width,
|
||||
bool barrierDismissible = true,
|
||||
}) {
|
||||
return showGeneralDialog<T>(
|
||||
context: context,
|
||||
barrierDismissible: barrierDismissible,
|
||||
barrierLabel: 'Drawer',
|
||||
barrierColor: Colors.black.withOpacity(0.5),
|
||||
transitionDuration: const Duration(milliseconds: 300),
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: SideDrawerPanel(title: title, width: width, child: child),
|
||||
);
|
||||
},
|
||||
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(begin: const Offset(1, 0), end: Offset.zero)
|
||||
.animate(
|
||||
CurvedAnimation(parent: animation, curve: Curves.easeInOut),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,7 @@ class ThemedFormField extends HookWidget {
|
||||
this.backgroundColor,
|
||||
this.highlightColor,
|
||||
this.borderColor,
|
||||
this.bordedRad,
|
||||
this.errorBorderColor,
|
||||
this.errorTextColor,
|
||||
this.readOnly = false, // ✅ new
|
||||
@ -37,6 +38,7 @@ class ThemedFormField extends HookWidget {
|
||||
|
||||
final double? verticalPad;
|
||||
final double? horizonalPad;
|
||||
final double? bordedRad;
|
||||
final double? enableBorderWidth;
|
||||
final String? hintText;
|
||||
final Color? textColr;
|
||||
@ -116,7 +118,7 @@ class ThemedFormField extends HookWidget {
|
||||
horizontal: 10,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: BorderRadius.circular(bordedRad ?? 10),
|
||||
// borderSide: BorderSide(color: Color(0xFF50A398)),
|
||||
borderSide: BorderSide(
|
||||
color: borderColor ?? Color(0xFFFFFFFF),
|
||||
@ -124,7 +126,7 @@ class ThemedFormField extends HookWidget {
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: BorderRadius.circular(bordedRad ?? 10),
|
||||
// borderSide: BorderSide(color: Color(0xFF50A398)),
|
||||
borderSide: BorderSide(
|
||||
color: borderColor ?? backgroundColor ?? Color(0xFFFFFFFF),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user