marco.pms.mobileapp/lib/view/document/user_document_screen.dart

319 lines
11 KiB
Dart

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<UserDocumentsPage> createState() => _UserDocumentsPageState();
}
class _UserDocumentsPageState extends State<UserDocumentsPage> {
final DocumentController docController = Get.put(DocumentController());
String get entityTypeId => widget.isEmployee
? Permissions.employeeEntity
: Permissions.projectEntity;
String get resolvedEntityId => widget.isEmployee
? widget.entityId ?? ""
: Get.find<ProjectController>().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: () {},
),
],
),
),
),
],
);
}
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) {
// Conditionally show AppBar (example: hide if employee view)
final bool showAppBar = !widget.isEmployee;
return Scaffold(
backgroundColor: const Color(0xFFF1F1F1),
appBar: showAppBar
? CustomAppBar(
title: 'Documents',
onBackPressed: () {
Get.back();
},
)
: null,
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) {
Navigator.pop(context);
docController.fetchDocuments(
entityTypeId: entityTypeId,
entityId: resolvedEntityId,
reset: true,
);
} else {
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,
);
}
}