import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:marco/controller/document/user_document_controller.dart'; import 'package:marco/controller/project_controller.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_refresh_indicator.dart'; import 'package:marco/helpers/utils/permission_constants.dart'; import 'package:marco/model/document/user_document_filter_bottom_sheet.dart'; import 'package:marco/model/document/documents_list_model.dart'; import 'package:intl/intl.dart'; import 'package:marco/helpers/widgets/my_custom_skeleton.dart'; import 'package:marco/model/document/document_upload_bottom_sheet.dart'; import 'package:marco/controller/document/document_upload_controller.dart'; import 'package:marco/view/document/document_details_page.dart'; import 'package:marco/helpers/widgets/custom_app_bar.dart'; class UserDocumentsPage extends StatefulWidget { final String? entityId; final bool isEmployee; const UserDocumentsPage({ super.key, this.entityId, this.isEmployee = false, }); @override State createState() => _UserDocumentsPageState(); } class _UserDocumentsPageState extends State { final DocumentController docController = Get.put(DocumentController()); String get entityTypeId => widget.isEmployee ? Permissions.employeeEntity : Permissions.projectEntity; String get resolvedEntityId => widget.isEmployee ? widget.entityId ?? "" : Get.find().selectedProject?.id ?? ""; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { docController.fetchFilters(entityTypeId); docController.fetchDocuments( entityTypeId: entityTypeId, entityId: resolvedEntityId, reset: true, ); }); } @override void dispose() { docController.documents.clear(); super.dispose(); } Widget _buildDocumentTile(DocumentItem doc) { final uploadDate = DateFormat("dd MMM yyyy").format(doc.uploadedAt.toLocal()); final uploader = doc.uploadedBy.firstName.isNotEmpty ? "Added by ${doc.uploadedBy.firstName} ${doc.uploadedBy.lastName}" .trim() : "Added by you"; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), child: MyText.bodySmall( uploadDate, fontSize: 13, fontWeight: 500, color: Colors.grey, ), ), InkWell( onTap: () { // πŸ‘‰ Navigate to details page Get.to(() => DocumentDetailsPage(documentId: doc.id)); }, child: Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(8), ), child: const Icon(Icons.description, color: Colors.blue), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.bodySmall( doc.documentType.name, fontSize: 13, fontWeight: 600, color: Colors.grey, ), MySpacing.height(2), MyText.bodyMedium( doc.name, fontSize: 15, fontWeight: 600, color: Colors.black, ), MySpacing.height(2), MyText.bodySmall( uploader, fontSize: 13, color: Colors.grey, ), ], ), ), IconButton( icon: const Icon(Icons.arrow_forward_ios, color: Colors.black54), onPressed: () {/* future actions */}, ), ], ), ), ), ], ); } Widget _buildEmptyState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.inbox_outlined, size: 60, color: Colors.grey), MySpacing.height(18), MyText.titleMedium( 'No documents found.', fontWeight: 600, color: Colors.grey, ), MySpacing.height(10), MyText.bodySmall( 'Try adjusting your filters or refresh to reload.', color: Colors.grey, ), ], ), ); } Widget _buildFilterRow(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), alignment: Alignment.centerRight, child: IconButton( icon: const Icon(Icons.tune, color: Colors.black), onPressed: () { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => UserDocumentFilterBottomSheet( entityId: resolvedEntityId, entityTypeId: entityTypeId, ), ); }, ), ); } Widget _buildBody(BuildContext context) { return Obx(() { if (docController.isLoading.value && docController.documents.isEmpty) { return SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), child: SkeletonLoaders.documentSkeletonLoader(), ); } final docs = docController.documents; return SafeArea( child: Column( children: [ _buildFilterRow(context), Expanded( child: MyRefreshIndicator( onRefresh: () async { await docController.fetchDocuments( entityTypeId: entityTypeId, entityId: resolvedEntityId, filter: docController.selectedFilter.value, reset: true, ); }, child: ListView( physics: const AlwaysScrollableScrollPhysics(), padding: docs.isEmpty ? null : const EdgeInsets.fromLTRB(0, 0, 0, 80), children: docs.isEmpty ? [ SizedBox( height: MediaQuery.of(context).size.height * 0.6, child: _buildEmptyState(), ), ] : [ ...docs.map(_buildDocumentTile), if (docController.isLoading.value) const Padding( padding: EdgeInsets.all(12), child: Center(child: CircularProgressIndicator()), ), if (!docController.hasMore.value) Padding( padding: const EdgeInsets.all(12), child: Center( child: MyText.bodySmall( "No more documents", color: Colors.grey, ), ), ), ], ), ), ), ], ), ); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF1F1F1), appBar: CustomAppBar( title: 'Documents', onBackPressed: () { Get.back(); }, ), body: _buildBody(context), floatingActionButton: FloatingActionButton.extended( onPressed: () { final uploadController = Get.put(DocumentUploadController()); showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => DocumentUploadBottomSheet( onSubmit: (data) async { final success = await uploadController.uploadDocument( name: data["name"], description: data["description"], documentId: data["documentId"], entityId: resolvedEntityId, documentTypeId: data["documentTypeId"], fileName: data["attachment"]["fileName"], base64Data: data["attachment"]["base64Data"], contentType: data["attachment"]["contentType"], fileSize: data["attachment"]["fileSize"], ); if (success) { // βœ… Only close on success Navigator.pop(context); // Refresh list docController.fetchDocuments( entityTypeId: entityTypeId, entityId: resolvedEntityId, reset: true, ); } else { // ❌ Don’t close, show error Get.snackbar("Error", "Upload failed, please try again"); } }, ), ); }, icon: const Icon(Icons.add, color: Colors.white), label: MyText.bodyMedium("Add Document", color: Colors.white, fontWeight: 600), backgroundColor: Colors.red, ), floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, ); } }