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';
class DocumentController extends GetxController {
// ------------------ Observables ---------------------
var isLoading = false.obs;
var documents = <DocumentItem>[].obs;
var filters = Rxn<DocumentFiltersData>();
// ==================== Observables ====================
final isLoading = false.obs;
final documents = <DocumentItem>[].obs;
final filters = Rxn<DocumentFiltersData>();
// Selected filters (multi-select support)
var selectedUploadedBy = <String>[].obs;
var selectedCategory = <String>[].obs;
var selectedType = <String>[].obs;
var selectedTag = <String>[].obs;
// Selected filters (multi-select)
final selectedUploadedBy = <String>[].obs;
final selectedCategory = <String>[].obs;
final selectedType = <String>[].obs;
final selectedTag = <String>[].obs;
// Pagination state
var pageNumber = 1.obs;
final int pageSize = 20;
var hasMore = true.obs;
// Pagination
final pageNumber = 1.obs;
final pageSize = 20;
final hasMore = true.obs;
// Error message
var errorMessage = "".obs;
// Error handling
final errorMessage = ''.obs;
// NEW: show inactive toggle
var showInactive = false.obs;
// Preferences
final showInactive = false.obs;
// NEW: search
var searchQuery = ''.obs;
var searchController = TextEditingController();
// New filter fields
var isUploadedAt = true.obs;
var isVerified = RxnBool();
var startDate = Rxn<String>();
var endDate = Rxn<String>();
// Search
final searchQuery = ''.obs;
final searchController = TextEditingController();
// ------------------ 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 {
try {
isLoading.value = true;
final response = await ApiService.getDocumentFilters(entityTypeId);
if (response != null && response.success) {
filters.value = response.data;
} else {
errorMessage.value = response?.message ?? "Failed to fetch filters";
errorMessage.value = response?.message ?? 'Failed to fetch filters';
_showError('Failed to load filters');
}
} catch (e) {
errorMessage.value = "Error fetching filters: $e";
} finally {
isLoading.value = false;
errorMessage.value = 'Error fetching filters: $e';
_showError('Error loading filters');
debugPrint('❌ Error fetching filters: $e');
}
}
@ -65,11 +74,14 @@ class DocumentController extends GetxController {
}) async {
try {
isLoading.value = true;
final success =
await ApiService.deleteDocumentApi(id: id, isActive: isActive);
final success = await ApiService.deleteDocumentApi(
id: id,
isActive: isActive,
);
if (success) {
// 🔥 Always fetch fresh list after toggle
// Refresh list after state change
await fetchDocuments(
entityTypeId: entityTypeId,
entityId: entityId,
@ -77,41 +89,19 @@ class DocumentController extends GetxController {
);
return true;
} else {
errorMessage.value = "Failed to update document state";
errorMessage.value = 'Failed to update document state';
return false;
}
} catch (e) {
errorMessage.value = "Error updating document: $e";
errorMessage.value = 'Error updating document: $e';
debugPrint('❌ Error toggling document state: $e');
return false;
} finally {
isLoading.value = false;
}
}
/// Permanently delete a document (or deactivate depending on API)
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
/// Fetch documents for entity with pagination
Future<void> fetchDocuments({
required String entityTypeId,
required String entityId,
@ -120,20 +110,25 @@ class DocumentController extends GetxController {
bool reset = false,
}) async {
try {
// Reset pagination if needed
if (reset) {
pageNumber.value = 1;
documents.clear();
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;
final response = await ApiService.getDocumentListApi(
entityTypeId: entityTypeId,
entityId: entityId,
filter: filter ?? "",
filter: filter ?? '',
searchString: searchString ?? searchQuery.value,
pageNumber: pageNumber.value,
pageSize: pageSize,
@ -147,19 +142,27 @@ class DocumentController extends GetxController {
} else {
hasMore.value = false;
}
errorMessage.value = '';
} 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) {
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 {
isLoading.value = false;
}
}
// ------------------ Helpers -----------------------
// ==================== Helper Methods ====================
/// Clear selected filters
/// Clear all selected filters
void clearFilters() {
selectedUploadedBy.clear();
selectedCategory.clear();
@ -171,11 +174,40 @@ class DocumentController extends GetxController {
endDate.value = null;
}
/// Check if any filters are active (for red dot indicator)
/// Check if any filters are active
bool hasActiveFilters() {
return selectedUploadedBy.isNotEmpty ||
selectedCategory.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/theme/admin_theme.dart';
import 'package:marco/helpers/theme/theme_customizer.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
class ThemeOption {
final String label;
@ -106,20 +105,6 @@ class _ThemeEditorWidgetState extends State<ThemeEditorWidget> {
],
),
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
Center(
child: Obx(

View File

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

File diff suppressed because it is too large Load Diff