Refactor theme editor widget and dashboard screen layout; enhance user document screen with improved search, filter, and document management features

This commit is contained in:
Vaibhav Surve 2025-10-31 14:57:29 +05:30
parent 3c89b4ddbb
commit d15d9f22df
4 changed files with 999 additions and 585 deletions

View File

@ -5,54 +5,63 @@ import 'package:marco/model/document/document_filter_model.dart';
import 'package:marco/model/document/documents_list_model.dart'; import 'package:marco/model/document/documents_list_model.dart';
class DocumentController extends GetxController { class DocumentController extends GetxController {
// ------------------ Observables --------------------- // ==================== Observables ====================
var isLoading = false.obs; final isLoading = false.obs;
var documents = <DocumentItem>[].obs; final documents = <DocumentItem>[].obs;
var filters = Rxn<DocumentFiltersData>(); final filters = Rxn<DocumentFiltersData>();
// Selected filters (multi-select support) // Selected filters (multi-select)
var selectedUploadedBy = <String>[].obs; final selectedUploadedBy = <String>[].obs;
var selectedCategory = <String>[].obs; final selectedCategory = <String>[].obs;
var selectedType = <String>[].obs; final selectedType = <String>[].obs;
var selectedTag = <String>[].obs; final selectedTag = <String>[].obs;
// Pagination state // Pagination
var pageNumber = 1.obs; final pageNumber = 1.obs;
final int pageSize = 20; final pageSize = 20;
var hasMore = true.obs; final hasMore = true.obs;
// Error message // Error handling
var errorMessage = "".obs; final errorMessage = ''.obs;
// NEW: show inactive toggle // Preferences
var showInactive = false.obs; final showInactive = false.obs;
// NEW: search // Search
var searchQuery = ''.obs; final searchQuery = ''.obs;
var searchController = TextEditingController(); final searchController = TextEditingController();
// New filter fields
var isUploadedAt = true.obs;
var isVerified = RxnBool();
var startDate = Rxn<String>();
var endDate = Rxn<String>();
// ------------------ API Calls ----------------------- // Additional filters
final isUploadedAt = true.obs;
final isVerified = RxnBool();
final startDate = Rxn<String>();
final endDate = Rxn<String>();
/// Fetch Document Filters for an Entity // ==================== Lifecycle ====================
@override
void onClose() {
// Don't dispose searchController here - it's managed by the page
super.onClose();
}
// ==================== API Methods ====================
/// Fetch document filters for entity
Future<void> fetchFilters(String entityTypeId) async { Future<void> fetchFilters(String entityTypeId) async {
try { try {
isLoading.value = true;
final response = await ApiService.getDocumentFilters(entityTypeId); final response = await ApiService.getDocumentFilters(entityTypeId);
if (response != null && response.success) { if (response != null && response.success) {
filters.value = response.data; filters.value = response.data;
} else { } else {
errorMessage.value = response?.message ?? "Failed to fetch filters"; errorMessage.value = response?.message ?? 'Failed to fetch filters';
_showError('Failed to load filters');
} }
} catch (e) { } catch (e) {
errorMessage.value = "Error fetching filters: $e"; errorMessage.value = 'Error fetching filters: $e';
} finally { _showError('Error loading filters');
isLoading.value = false; debugPrint('❌ Error fetching filters: $e');
} }
} }
@ -65,11 +74,14 @@ class DocumentController extends GetxController {
}) async { }) async {
try { try {
isLoading.value = true; isLoading.value = true;
final success =
await ApiService.deleteDocumentApi(id: id, isActive: isActive); final success = await ApiService.deleteDocumentApi(
id: id,
isActive: isActive,
);
if (success) { if (success) {
// 🔥 Always fetch fresh list after toggle // Refresh list after state change
await fetchDocuments( await fetchDocuments(
entityTypeId: entityTypeId, entityTypeId: entityTypeId,
entityId: entityId, entityId: entityId,
@ -77,41 +89,19 @@ class DocumentController extends GetxController {
); );
return true; return true;
} else { } else {
errorMessage.value = "Failed to update document state"; errorMessage.value = 'Failed to update document state';
return false; return false;
} }
} catch (e) { } catch (e) {
errorMessage.value = "Error updating document: $e"; errorMessage.value = 'Error updating document: $e';
debugPrint('❌ Error toggling document state: $e');
return false; return false;
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
} }
/// Permanently delete a document (or deactivate depending on API) /// Fetch documents for entity with pagination
Future<bool> deleteDocument(String id, {bool isActive = false}) async {
try {
isLoading.value = true;
final success =
await ApiService.deleteDocumentApi(id: id, isActive: isActive);
if (success) {
// remove from local list immediately for better UX
documents.removeWhere((doc) => doc.id == id);
return true;
} else {
errorMessage.value = "Failed to delete document";
return false;
}
} catch (e) {
errorMessage.value = "Error deleting document: $e";
return false;
} finally {
isLoading.value = false;
}
}
/// Fetch Documents for an entity
Future<void> fetchDocuments({ Future<void> fetchDocuments({
required String entityTypeId, required String entityTypeId,
required String entityId, required String entityId,
@ -120,20 +110,25 @@ class DocumentController extends GetxController {
bool reset = false, bool reset = false,
}) async { }) async {
try { try {
// Reset pagination if needed
if (reset) { if (reset) {
pageNumber.value = 1; pageNumber.value = 1;
documents.clear(); documents.clear();
hasMore.value = true; hasMore.value = true;
} }
if (!hasMore.value) return; // Don't fetch if no more data
if (!hasMore.value && !reset) return;
// Prevent duplicate requests
if (isLoading.value) return;
isLoading.value = true; isLoading.value = true;
final response = await ApiService.getDocumentListApi( final response = await ApiService.getDocumentListApi(
entityTypeId: entityTypeId, entityTypeId: entityTypeId,
entityId: entityId, entityId: entityId,
filter: filter ?? "", filter: filter ?? '',
searchString: searchString ?? searchQuery.value, searchString: searchString ?? searchQuery.value,
pageNumber: pageNumber.value, pageNumber: pageNumber.value,
pageSize: pageSize, pageSize: pageSize,
@ -147,19 +142,27 @@ class DocumentController extends GetxController {
} else { } else {
hasMore.value = false; hasMore.value = false;
} }
errorMessage.value = '';
} else { } else {
errorMessage.value = response?.message ?? "Failed to fetch documents"; errorMessage.value = response?.message ?? 'Failed to fetch documents';
if (documents.isEmpty) {
_showError('Failed to load documents');
}
} }
} catch (e) { } catch (e) {
errorMessage.value = "Error fetching documents: $e"; errorMessage.value = 'Error fetching documents: $e';
if (documents.isEmpty) {
_showError('Error loading documents');
}
debugPrint('❌ Error fetching documents: $e');
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
} }
// ------------------ Helpers ----------------------- // ==================== Helper Methods ====================
/// Clear selected filters /// Clear all selected filters
void clearFilters() { void clearFilters() {
selectedUploadedBy.clear(); selectedUploadedBy.clear();
selectedCategory.clear(); selectedCategory.clear();
@ -171,11 +174,40 @@ class DocumentController extends GetxController {
endDate.value = null; endDate.value = null;
} }
/// Check if any filters are active (for red dot indicator) /// Check if any filters are active
bool hasActiveFilters() { bool hasActiveFilters() {
return selectedUploadedBy.isNotEmpty || return selectedUploadedBy.isNotEmpty ||
selectedCategory.isNotEmpty || selectedCategory.isNotEmpty ||
selectedType.isNotEmpty || selectedType.isNotEmpty ||
selectedTag.isNotEmpty; selectedTag.isNotEmpty ||
startDate.value != null ||
endDate.value != null ||
isVerified.value != null;
}
/// Show error message
void _showError(String message) {
Get.snackbar(
'Error',
message,
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red.shade100,
colorText: Colors.red.shade900,
margin: const EdgeInsets.all(16),
borderRadius: 8,
duration: const Duration(seconds: 3),
);
}
/// Reset controller state
void reset() {
documents.clear();
clearFilters();
searchController.clear();
searchQuery.value = '';
pageNumber.value = 1;
hasMore.value = true;
showInactive.value = false;
errorMessage.value = '';
} }
} }

View File

@ -4,7 +4,6 @@ import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/helpers/widgets/wave_background.dart'; import 'package:marco/helpers/widgets/wave_background.dart';
import 'package:marco/helpers/theme/admin_theme.dart'; import 'package:marco/helpers/theme/admin_theme.dart';
import 'package:marco/helpers/theme/theme_customizer.dart'; import 'package:marco/helpers/theme/theme_customizer.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
class ThemeOption { class ThemeOption {
final String label; final String label;
@ -106,20 +105,6 @@ class _ThemeEditorWidgetState extends State<ThemeEditorWidget> {
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
InkWell(
onTap: () {
ThemeCustomizer.setTheme(
ThemeCustomizer.instance.theme == ThemeMode.dark
? ThemeMode.light
: ThemeMode.dark);
},
child: Icon(
ThemeCustomizer.instance.theme == ThemeMode.dark
? LucideIcons.sun
: LucideIcons.moon,
size: 18,
),
),
// Theme cards wrapped in reactive Obx widget // Theme cards wrapped in reactive Obx widget
Center( Center(
child: Obx( child: Obx(

View File

@ -62,6 +62,10 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
children: [ children: [
_buildDashboardStats(context), _buildDashboardStats(context),
MySpacing.height(24), MySpacing.height(24),
_buildAttendanceChartSection(),
MySpacing.height(24),
_buildProjectProgressChartSection(),
MySpacing.height(24),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: DashboardOverviewWidgets.teamsOverview(), child: DashboardOverviewWidgets.teamsOverview(),
@ -71,10 +75,6 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
width: double.infinity, width: double.infinity,
child: DashboardOverviewWidgets.tasksOverview(), child: DashboardOverviewWidgets.tasksOverview(),
), ),
MySpacing.height(24),
_buildAttendanceChartSection(),
MySpacing.height(24),
_buildProjectProgressChartSection(),
], ],
), ),
), ),

File diff suppressed because it is too large Load Diff