handelled the update expense

This commit is contained in:
Vaibhav Surve 2025-08-06 11:46:12 +05:30
parent aa76ec60cb
commit 63e5caae24
2 changed files with 171 additions and 149 deletions

View File

@ -170,8 +170,14 @@ class AddExpenseController extends GetxController {
// --- Existing Attachments ---
existingAttachments.clear();
if (data['attachments'] != null && data['attachments'] is List) {
existingAttachments
.addAll(List<Map<String, dynamic>>.from(data['attachments']));
existingAttachments.addAll(
List<Map<String, dynamic>>.from(data['attachments']).map((e) {
return {
...e,
'isActive': true, // default
};
}),
);
}
_logPrefilledData();
@ -369,15 +375,20 @@ class AddExpenseController extends GetxController {
final projectId = projectsMap[selectedProject.value]!;
final selectedDate =
selectedTransactionDate.value?.toUtc() ?? DateTime.now().toUtc();
final existingAttachmentPayloads = existingAttachments
.map((e) => {
"fileName": e['fileName'],
"contentType": e['contentType'],
"fileSize": 0, // optional or populate if known
"description": "",
"url": e['url'], // custom field if your backend accepts
})
.toList();
final existingAttachmentPayloads = existingAttachments.map((e) {
final isActive = e['isActive'] ?? true;
return {
"documentId": e['documentId'],
"fileName": e['fileName'],
"contentType": e['contentType'],
"fileSize": 0,
"description": "",
"url": e['url'],
"isActive": isActive,
"base64Data": isActive ? e['base64Data'] : null,
};
}).toList();
final newAttachmentPayloads =
await Future.wait(attachments.map((file) async {

View File

@ -41,22 +41,20 @@ class _AddExpenseBottomSheetState extends State<_AddExpenseBottomSheet> {
final AddExpenseController controller = Get.put(AddExpenseController());
void _showEmployeeList() async {
await showModalBottomSheet(
context: context,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
backgroundColor: Colors.transparent,
builder: (_) => EmployeeSelectorBottomSheet(),
);
// Optional cleanup
controller.employeeSearchController.clear();
controller.employeeSearchResults.clear();
}
await showModalBottomSheet(
context: context,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
backgroundColor: Colors.transparent,
builder: (_) => EmployeeSelectorBottomSheet(),
);
// Optional cleanup
controller.employeeSearchController.clear();
controller.employeeSearchResults.clear();
}
Future<void> _showOptionList<T>(
List<T> options,
@ -277,8 +275,13 @@ class _AddExpenseBottomSheetState extends State<_AddExpenseBottomSheet> {
attachments: controller.attachments,
existingAttachments: controller.existingAttachments,
onRemoveNew: controller.removeAttachment,
onRemoveExisting: (item) =>
controller.existingAttachments.remove(item),
onRemoveExisting: (item) {
final index = controller.existingAttachments.indexOf(item);
if (index != -1) {
controller.existingAttachments[index]['isActive'] = false;
controller.existingAttachments.refresh();
}
},
onAdd: controller.pickAttachments,
),
MySpacing.height(16),
@ -458,7 +461,7 @@ class _TileContainer extends StatelessWidget {
class _AttachmentsSection extends StatelessWidget {
final RxList<File> attachments;
final List<Map<String, dynamic>> existingAttachments;
final RxList<Map<String, dynamic>> existingAttachments;
final ValueChanged<File> onRemoveNew;
final ValueChanged<Map<String, dynamic>>? onRemoveExisting;
final VoidCallback onAdd;
@ -473,133 +476,141 @@ class _AttachmentsSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Obx(() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (existingAttachments.isNotEmpty) ...[
Text(
"Existing Attachments",
style: const TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: existingAttachments.map((doc) {
final isImage =
doc['contentType']?.toString().startsWith('image/') ??
false;
final url = doc['url'];
final fileName = doc['fileName'] ?? 'Unnamed';
return Obx(() {
final activeExistingAttachments =
existingAttachments.where((doc) => doc['isActive'] != false).toList();
return Stack(
clipBehavior: Clip.none,
children: [
GestureDetector(
onTap: () async {
if (isImage) {
final imageDocs = existingAttachments
.where((d) => (d['contentType']
?.toString()
.startsWith('image/') ??
false))
.toList();
final initialIndex =
imageDocs.indexWhere((d) => d == doc);
showDialog(
context: context,
builder: (_) => ImageViewerDialog(
imageSources:
imageDocs.map((e) => e['url']).toList(),
initialIndex: initialIndex,
),
);
} else {
if (url != null && await canLaunchUrlString(url)) {
await launchUrlString(url,
mode: LaunchMode.externalApplication);
} else {
showAppSnackbar(
title: 'Error',
message: 'Could not open the document.',
type: SnackbarType.error,
);
}
}
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 8),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(6),
color: Colors.grey.shade100,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isImage ? Icons.image : Icons.insert_drive_file,
size: 20,
color: Colors.grey[600],
),
const SizedBox(width: 7),
ConstrainedBox(
constraints:
const BoxConstraints(maxWidth: 120),
child: Text(
fileName,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12),
),
),
],
),
),
),
if (onRemoveExisting != null)
Positioned(
top: -6,
right: -6,
child: IconButton(
icon: const Icon(Icons.close,
color: Colors.red, size: 18),
onPressed: () => onRemoveExisting!(doc),
),
),
],
);
}).toList(),
),
const SizedBox(height: 16),
],
// New attachments section - shows preview tiles
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (activeExistingAttachments.isNotEmpty) ...[
Text(
"Existing Attachments",
style: const TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
...attachments.map((file) => _AttachmentTile(
file: file,
onRemove: () => onRemoveNew(file),
)),
GestureDetector(
onTap: onAdd,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade400),
borderRadius: BorderRadius.circular(8),
color: Colors.grey.shade100,
children: activeExistingAttachments.map((doc) {
final isImage =
doc['contentType']?.toString().startsWith('image/') ??
false;
final url = doc['url'];
final fileName = doc['fileName'] ?? 'Unnamed';
return Stack(
clipBehavior: Clip.none,
children: [
GestureDetector(
onTap: () async {
if (isImage) {
final imageDocs = activeExistingAttachments
.where((d) => (d['contentType']
?.toString()
.startsWith('image/') ??
false))
.toList();
final initialIndex =
imageDocs.indexWhere((d) => d == doc);
showDialog(
context: context,
builder: (_) => ImageViewerDialog(
imageSources:
imageDocs.map((e) => e['url']).toList(),
initialIndex: initialIndex,
),
);
} else {
if (url != null && await canLaunchUrlString(url)) {
await launchUrlString(
url,
mode: LaunchMode.externalApplication,
);
} else {
showAppSnackbar(
title: 'Error',
message: 'Could not open the document.',
type: SnackbarType.error,
);
}
}
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 8),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(6),
color: Colors.grey.shade100,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isImage ? Icons.image : Icons.insert_drive_file,
size: 20,
color: Colors.grey[600],
),
const SizedBox(width: 7),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 120),
child: Text(
fileName,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12),
),
),
],
),
),
),
child: const Icon(Icons.add, size: 30, color: Colors.grey),
),
),
],
if (onRemoveExisting != null)
Positioned(
top: -6,
right: -6,
child: IconButton(
icon: const Icon(Icons.close,
color: Colors.red, size: 18),
onPressed: () {
onRemoveExisting?.call(doc);
},
),
),
],
);
}).toList(),
),
const SizedBox(height: 16),
],
));
// New attachments section
Wrap(
spacing: 8,
runSpacing: 8,
children: [
...attachments.map((file) => _AttachmentTile(
file: file,
onRemove: () => onRemoveNew(file),
)),
GestureDetector(
onTap: onAdd,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade400),
borderRadius: BorderRadius.circular(8),
color: Colors.grey.shade100,
),
child: const Icon(Icons.add, size: 30, color: Colors.grey),
),
),
],
),
],
);
});
}
}