feat: Implement daily task planning and progress reporting features

- Added TaskListModel for managing daily tasks with JSON parsing.
- Introduced WorkStatusResponseModel and WorkStatus for handling work status data.
- Created MenuResponse and MenuItem models for dynamic menu management.
- Updated routes to reflect correct naming conventions for task planning screens.
- Enhanced DashboardScreen to include dynamic menu functionality and improved task statistics display.
- Developed DailyProgressReportScreen for displaying daily progress reports with filtering options.
- Implemented DailyTaskPlanningScreen for planning daily tasks with detailed views and actions.
- Refactored left navigation bar to align with updated task planning routes.
This commit is contained in:
Vaibhav Surve 2025-08-28 14:48:05 +05:30
parent 91184b48bb
commit a154872649
30 changed files with 462 additions and 185 deletions

View File

@ -3,7 +3,7 @@ import 'package:get/get.dart';
import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/helpers/services/app_logger.dart';
import 'package:marco/helpers/services/api_service.dart'; import 'package:marco/helpers/services/api_service.dart';
import 'package:marco/model/project_model.dart'; import 'package:marco/model/project_model.dart';
import 'package:marco/model/dailyTaskPlaning/daily_task_model.dart'; import 'package:marco/model/dailyTaskPlanning/daily_task_model.dart';
class DailyTaskController extends GetxController { class DailyTaskController extends GetxController {
List<ProjectModel> projects = []; List<ProjectModel> projects = [];

View File

@ -0,0 +1,86 @@
import 'dart:async';
import 'package:get/get.dart';
import 'package:marco/model/dynamicMenu/dynamic_menu_model.dart';
import 'package:marco/helpers/services/api_service.dart';
import 'package:marco/helpers/services/app_logger.dart';
import 'package:marco/helpers/services/storage/local_storage.dart';
class DynamicMenuController extends GetxController {
// UI reactive states
final RxBool isLoading = false.obs;
final RxBool hasError = false.obs;
final RxString errorMessage = ''.obs;
final RxList<MenuItem> menuItems = <MenuItem>[].obs;
Timer? _autoRefreshTimer;
@override
void onInit() {
super.onInit();
fetchMenu();
/// Auto refresh every 5 minutes (adjust as needed)
_autoRefreshTimer = Timer.periodic(
const Duration(minutes: 1),
(_) => fetchMenu(),
);
}
/// Fetch dynamic menu from API with error and local storage support
Future<void> fetchMenu() async {
isLoading.value = true;
hasError.value = false;
errorMessage.value = '';
try {
final responseData = await ApiService.getMenuApi();
if (responseData != null) {
// Directly parse full JSON into MenuResponse
final menuResponse = MenuResponse.fromJson(responseData);
menuItems.assignAll(menuResponse.data);
// Save menus for offline use
await LocalStorage.setMenus(menuItems);
logSafe("Menu loaded from API with ${menuItems.length} items");
} else {
// If API fails, load from cache
final cachedMenus = LocalStorage.getMenus();
if (cachedMenus.isNotEmpty) {
menuItems.assignAll(cachedMenus);
logSafe("Loaded menus from cache: ${menuItems.length} items");
} else {
hasError.value = true;
errorMessage.value = "Failed to fetch menu";
menuItems.clear();
}
}
} catch (e) {
logSafe("Menu fetch exception: $e", level: LogLevel.error);
// On error, load cached menus
final cachedMenus = LocalStorage.getMenus();
if (cachedMenus.isNotEmpty) {
menuItems.assignAll(cachedMenus);
logSafe("Loaded menus from cache after error: ${menuItems.length}");
} else {
hasError.value = true;
errorMessage.value = e.toString();
menuItems.clear();
}
} finally {
isLoading.value = false;
}
}
bool isMenuAllowed(String menuName) {
final menu = menuItems.firstWhereOrNull((m) => m.name == menuName);
return menu?.available ?? false; // default false if not found
}
@override
void onClose() {
_autoRefreshTimer?.cancel(); // clean up timer
super.onClose();
}
}

View File

@ -3,7 +3,7 @@ import 'package:marco/helpers/services/app_logger.dart';
import 'package:marco/helpers/services/api_service.dart'; import 'package:marco/helpers/services/api_service.dart';
import 'package:marco/helpers/widgets/my_form_validator.dart'; import 'package:marco/helpers/widgets/my_form_validator.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart';
import 'package:marco/model/dailyTaskPlaning/master_work_category_model.dart'; import 'package:marco/model/dailyTaskPlanning/master_work_category_model.dart';
class AddTaskController extends GetxController { class AddTaskController extends GetxController {
RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs; RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs;

View File

@ -4,10 +4,10 @@ import 'package:marco/helpers/services/api_service.dart';
import 'package:marco/helpers/widgets/my_form_validator.dart'; import 'package:marco/helpers/widgets/my_form_validator.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart';
import 'package:marco/model/project_model.dart'; import 'package:marco/model/project_model.dart';
import 'package:marco/model/dailyTaskPlaning/daily_task_planing_model.dart'; import 'package:marco/model/dailyTaskPlanning/daily_task_planning_model.dart';
import 'package:marco/model/employees/employee_model.dart'; import 'package:marco/model/employees/employee_model.dart';
class DailyTaskPlaningController extends GetxController { class DailyTaskPlanningController extends GetxController {
List<ProjectModel> projects = []; List<ProjectModel> projects = [];
List<EmployeeModel> employees = []; List<EmployeeModel> employees = [];
List<TaskPlanningDetailsModel> dailyTasks = []; List<TaskPlanningDetailsModel> dailyTasks = [];

View File

@ -7,12 +7,12 @@ import 'package:image_picker/image_picker.dart';
import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/helpers/services/app_logger.dart';
import 'package:marco/controller/my_controller.dart'; import 'package:marco/controller/my_controller.dart';
import 'package:marco/controller/task_planing/daily_task_planing_controller.dart'; import 'package:marco/controller/task_Planning/daily_task_Planning_controller.dart';
import 'package:marco/helpers/services/api_service.dart'; import 'package:marco/helpers/services/api_service.dart';
import 'package:marco/helpers/widgets/my_form_validator.dart'; import 'package:marco/helpers/widgets/my_form_validator.dart';
import 'package:marco/helpers/widgets/my_image_compressor.dart'; import 'package:marco/helpers/widgets/my_image_compressor.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart';
import 'package:marco/model/dailyTaskPlaning/work_status_model.dart'; import 'package:marco/model/dailyTaskPlanning/work_status_model.dart';
enum ApiStatus { idle, loading, success, failure } enum ApiStatus { idle, loading, success, failure }
@ -34,7 +34,7 @@ class ReportTaskActionController extends MyController {
final RxString selectedWorkStatusName = ''.obs; final RxString selectedWorkStatusName = ''.obs;
final MyFormValidator basicValidator = MyFormValidator(); final MyFormValidator basicValidator = MyFormValidator();
final DailyTaskPlaningController taskController = Get.put(DailyTaskPlaningController()); final DailyTaskPlanningController taskController = Get.put(DailyTaskPlanningController());
final ImagePicker _picker = ImagePicker(); final ImagePicker _picker = ImagePicker();
final assignedDateController = TextEditingController(); final assignedDateController = TextEditingController();

View File

@ -6,7 +6,7 @@ import 'package:marco/helpers/services/api_service.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/helpers/services/app_logger.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart';
import 'package:marco/controller/task_planing/daily_task_planing_controller.dart'; import 'package:marco/controller/task_Planning/daily_task_Planning_controller.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'dart:io'; import 'dart:io';
import 'dart:convert'; import 'dart:convert';
@ -14,7 +14,7 @@ import 'package:marco/helpers/widgets/my_image_compressor.dart';
enum ApiStatus { idle, loading, success, failure } enum ApiStatus { idle, loading, success, failure }
final DailyTaskPlaningController taskController = Get.put(DailyTaskPlaningController()); final DailyTaskPlanningController taskController = Get.put(DailyTaskPlanningController());
final ImagePicker _picker = ImagePicker(); final ImagePicker _picker = ImagePicker();
class ReportTaskController extends MyController { class ReportTaskController extends MyController {

View File

@ -1,6 +1,6 @@
class ApiEndpoints { class ApiEndpoints {
static const String baseUrl = "https://stageapi.marcoaiot.com/api"; // static const String baseUrl = "https://stageapi.marcoaiot.com/api";
// static const String baseUrl = "https://api.marcoaiot.com/api"; static const String baseUrl = "https://api.marcoaiot.com/api";
// Dashboard Module API Endpoints // Dashboard Module API Endpoints
static const String getDashboardAttendanceOverview = "/dashboard/attendance-overview"; static const String getDashboardAttendanceOverview = "/dashboard/attendance-overview";
@ -62,4 +62,7 @@ class ApiEndpoints {
static const String getMasterExpenseTypes = "/master/expenses-types"; static const String getMasterExpenseTypes = "/master/expenses-types";
static const String updateExpenseStatus = "/expense/action"; static const String updateExpenseStatus = "/expense/action";
static const String deleteExpense = "/expense/delete"; static const String deleteExpense = "/expense/delete";
////// Dynamic Menu Module API Endpoints
static const String getDynamicMenu = "/appmenu/get/menu-mobile";
} }

View File

@ -239,6 +239,48 @@ class ApiService {
} }
} }
// === Menu APIs === //
/// Get Sidebar Menu API
static Future<Map<String, dynamic>?> getMenuApi() async {
logSafe("Fetching sidebar menu...");
try {
final response = await _getRequest(ApiEndpoints.getDynamicMenu);
if (response == null) {
logSafe("Menu request failed: null response", level: LogLevel.error);
return null;
}
final body = response.body.trim();
if (body.isEmpty) {
logSafe("Menu response body is empty", level: LogLevel.error);
return null;
}
final jsonResponse = jsonDecode(body);
if (jsonResponse is Map<String, dynamic>) {
if (jsonResponse['success'] == true) {
logSafe("Sidebar menu fetched successfully");
return jsonResponse; // return full response
} else {
logSafe(
"Failed to fetch sidebar menu: ${jsonResponse['message'] ?? 'Unknown error'}",
level: LogLevel.warning,
);
}
} else {
logSafe("Unexpected response structure: $jsonResponse",
level: LogLevel.error);
}
} catch (e, stack) {
logSafe("Exception during getMenuApi: $e", level: LogLevel.error);
logSafe("StackTrace: $stack", level: LogLevel.debug);
}
return null;
}
// === Expense APIs === // // === Expense APIs === //
/// Edit Expense API /// Edit Expense API
@ -1085,7 +1127,7 @@ class ApiService {
? _parseResponse(res, label: 'Regularization Logs') ? _parseResponse(res, label: 'Regularization Logs')
: null); : null);
static Future<bool> uploadAttendanceImage( static Future<bool> uploadAttendanceImage(
String id, String id,
String employeeId, String employeeId,
XFile? imageFile, XFile? imageFile,
@ -1098,7 +1140,7 @@ static Future<bool> uploadAttendanceImage(
bool imageCapture = true, bool imageCapture = true,
required String markTime, // 👈 now required required String markTime, // 👈 now required
required String date, // 👈 new required param required String date, // 👈 new required param
}) async { }) async {
final body = { final body = {
"id": id, "id": id,
"employeeId": employeeId, "employeeId": employeeId,
@ -1142,8 +1184,7 @@ static Future<bool> uploadAttendanceImage(
logSafe("Failed to upload image: ${json['message'] ?? 'Unknown error'}"); logSafe("Failed to upload image: ${json['message'] ?? 'Unknown error'}");
return false; return false;
} }
static String generateImageName(String employeeId, int count) { static String generateImageName(String employeeId, int count) {
final now = DateTime.now(); final now = DateTime.now();

View File

@ -3,7 +3,7 @@ import 'package:logger/logger.dart';
import 'package:marco/controller/dashboard/attendance_screen_controller.dart'; import 'package:marco/controller/dashboard/attendance_screen_controller.dart';
import 'package:marco/controller/dashboard/daily_task_controller.dart'; import 'package:marco/controller/dashboard/daily_task_controller.dart';
import 'package:marco/controller/task_planing/daily_task_planing_controller.dart'; import 'package:marco/controller/task_Planning/daily_task_Planning_controller.dart';
import 'package:marco/controller/expense/expense_screen_controller.dart'; import 'package:marco/controller/expense/expense_screen_controller.dart';
import 'package:marco/controller/expense/expense_detail_controller.dart'; import 'package:marco/controller/expense/expense_detail_controller.dart';
@ -88,14 +88,14 @@ class NotificationActionHandler {
return; return;
} }
_safeControllerUpdate<DailyTaskPlaningController>( _safeControllerUpdate<DailyTaskPlanningController>(
onFound: (controller) { onFound: (controller) {
controller.fetchTaskData(projectId); controller.fetchTaskData(projectId);
}, },
notFoundMessage: notFoundMessage:
'⚠️ DailyTaskPlaningController not found, cannot refresh.', '⚠️ DailyTaskPlanningController not found, cannot refresh.',
successMessage: successMessage:
'✅ DailyTaskPlaningController refreshed from notification.', '✅ DailyTaskPlanningController refreshed from notification.',
); );
} }

View File

@ -7,6 +7,7 @@ import 'package:marco/helpers/services/localizations/language.dart';
import 'package:marco/helpers/theme/theme_customizer.dart'; import 'package:marco/helpers/theme/theme_customizer.dart';
import 'package:marco/model/employees/employee_info.dart'; import 'package:marco/model/employees/employee_info.dart';
import 'package:marco/model/user_permission.dart'; import 'package:marco/model/user_permission.dart';
import 'package:marco/model/dynamicMenu/dynamic_menu_model.dart';
class LocalStorage { class LocalStorage {
static const String _loggedInUserKey = "user"; static const String _loggedInUserKey = "user";
@ -19,6 +20,7 @@ class LocalStorage {
static const String _mpinTokenKey = "mpinToken"; static const String _mpinTokenKey = "mpinToken";
static const String _isMpinKey = "isMpin"; static const String _isMpinKey = "isMpin";
static const String _fcmTokenKey = 'fcm_token'; static const String _fcmTokenKey = 'fcm_token';
static const String _menuStorageKey = "dynamic_menus";
static SharedPreferences? _preferencesInstance; static SharedPreferences? _preferencesInstance;
@ -39,6 +41,30 @@ class LocalStorage {
AuthService.isLoggedIn = preferences.getBool(_loggedInUserKey) ?? false; AuthService.isLoggedIn = preferences.getBool(_loggedInUserKey) ?? false;
ThemeCustomizer.fromJSON(preferences.getString(_themeCustomizerKey)); ThemeCustomizer.fromJSON(preferences.getString(_themeCustomizerKey));
} }
/// ================== Sidebar Menu ==================
static Future<bool> setMenus(List<MenuItem> menus) async {
try {
final jsonList = menus.map((e) => e.toJson()).toList();
return preferences.setString(_menuStorageKey, jsonEncode(jsonList));
} catch (e) {
print("Error saving menus: $e");
return false;
}
}
static List<MenuItem> getMenus() {
final storedJson = preferences.getString(_menuStorageKey);
if (storedJson == null) return [];
try {
return (jsonDecode(storedJson) as List)
.map((e) => MenuItem.fromJson(e as Map<String, dynamic>))
.toList();
} catch (e) {
print("Error loading menus: $e");
return [];
}
}
static Future<bool> removeMenus() => preferences.remove(_menuStorageKey);
/// ================== User Permissions ================== /// ================== User Permissions ==================
static Future<bool> setUserPermissions( static Future<bool> setUserPermissions(
@ -59,8 +85,8 @@ class LocalStorage {
preferences.remove(_userPermissionsKey); preferences.remove(_userPermissionsKey);
/// ================== Employee Info ================== /// ================== Employee Info ==================
static Future<bool> setEmployeeInfo(EmployeeInfo employeeInfo) => preferences static Future<bool> setEmployeeInfo(EmployeeInfo employeeInfo) =>
.setString(_employeeInfoKey, jsonEncode(employeeInfo.toJson())); preferences.setString(_employeeInfoKey, jsonEncode(employeeInfo.toJson()));
static EmployeeInfo? getEmployeeInfo() { static EmployeeInfo? getEmployeeInfo() {
final storedJson = preferences.getString(_employeeInfoKey); final storedJson = preferences.getString(_employeeInfoKey);
@ -92,7 +118,7 @@ class LocalStorage {
print("Logout API error: $e"); print("Logout API error: $e");
} }
// ===== Local Cleanup ===== /// ===== Local Cleanup =====
await removeLoggedInUser(); await removeLoggedInUser();
await removeToken(_jwtTokenKey); await removeToken(_jwtTokenKey);
await removeToken(_refreshTokenKey); await removeToken(_refreshTokenKey);
@ -100,6 +126,7 @@ class LocalStorage {
await removeEmployeeInfo(); await removeEmployeeInfo();
await removeMpinToken(); await removeMpinToken();
await removeIsMpin(); await removeIsMpin();
await removeMenus(); // clear menus on logout
await preferences.remove("mpin_verified"); await preferences.remove("mpin_verified");
await preferences.remove(_languageKey); await preferences.remove(_languageKey);
await preferences.remove(_themeCustomizerKey); await preferences.remove(_themeCustomizerKey);

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:marco/controller/task_planing/daily_task_planing_controller.dart'; import 'package:marco/controller/task_Planning/daily_task_Planning_controller.dart';
import 'package:marco/controller/project_controller.dart'; import 'package:marco/controller/project_controller.dart';
import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text.dart';
@ -34,7 +34,7 @@ class AssignTaskBottomSheet extends StatefulWidget {
} }
class _AssignTaskBottomSheetState extends State<AssignTaskBottomSheet> { class _AssignTaskBottomSheetState extends State<AssignTaskBottomSheet> {
final DailyTaskPlaningController controller = Get.find(); final DailyTaskPlanningController controller = Get.find();
final ProjectController projectController = Get.find(); final ProjectController projectController = Get.find();
final TextEditingController targetController = TextEditingController(); final TextEditingController targetController = TextEditingController();
final TextEditingController descriptionController = TextEditingController(); final TextEditingController descriptionController = TextEditingController();

View File

@ -4,7 +4,7 @@ import 'package:intl/intl.dart';
import 'dart:io'; import 'dart:io';
import 'dart:math' as math; import 'dart:math' as math;
// --- Assumed Imports (ensure these paths are correct in your project) --- // --- Assumed Imports (ensure these paths are correct in your project) ---
import 'package:marco/controller/task_planing/report_task_controller.dart'; import 'package:marco/controller/task_planning/report_task_controller.dart';
import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
import 'package:marco/helpers/widgets/my_button.dart'; import 'package:marco/helpers/widgets/my_button.dart';
import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_spacing.dart';
@ -13,7 +13,7 @@ import 'package:marco/helpers/widgets/my_text_style.dart';
import 'package:marco/helpers/widgets/avatar.dart'; import 'package:marco/helpers/widgets/avatar.dart';
import 'package:marco/helpers/widgets/my_team_model_sheet.dart'; import 'package:marco/helpers/widgets/my_team_model_sheet.dart';
import 'package:marco/helpers/widgets/image_viewer_dialog.dart'; import 'package:marco/helpers/widgets/image_viewer_dialog.dart';
import 'package:marco/model/dailyTaskPlaning/create_task_botom_sheet.dart'; import 'package:marco/model/dailyTaskPlanning/create_task_botom_sheet.dart';
import 'package:marco/helpers/utils/base_bottom_sheet.dart'; import 'package:marco/helpers/utils/base_bottom_sheet.dart';
// --- Form Field Keys (Unchanged) --- // --- Form Field Keys (Unchanged) ---

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:marco/controller/task_planing/add_task_controller.dart'; import 'package:marco/controller/task_planning/add_task_controller.dart';
import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart';

View File

@ -1,13 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:marco/controller/permission_controller.dart'; import 'package:marco/controller/permission_controller.dart';
import 'package:marco/controller/task_planing/daily_task_planing_controller.dart'; import 'package:marco/controller/task_Planning/daily_task_Planning_controller.dart';
import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text.dart';
class DailyTaskPlaningFilter extends StatelessWidget { class DailyTaskPlanningFilter extends StatelessWidget {
final DailyTaskPlaningController controller; final DailyTaskPlanningController controller;
final PermissionController permissionController; final PermissionController permissionController;
const DailyTaskPlaningFilter({ const DailyTaskPlanningFilter({
super.key, super.key,
required this.controller, required this.controller,
required this.permissionController, required this.permissionController,

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:marco/controller/task_planing/report_task_action_controller.dart'; import 'package:marco/controller/task_planning/report_task_action_controller.dart';
import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text.dart';
@ -8,8 +8,8 @@ import 'package:marco/helpers/widgets/my_text_style.dart';
import 'package:marco/helpers/widgets/avatar.dart'; import 'package:marco/helpers/widgets/avatar.dart';
import 'package:marco/helpers/widgets/my_team_model_sheet.dart'; import 'package:marco/helpers/widgets/my_team_model_sheet.dart';
import 'package:marco/helpers/widgets/image_viewer_dialog.dart'; import 'package:marco/helpers/widgets/image_viewer_dialog.dart';
import 'package:marco/model/dailyTaskPlaning/create_task_botom_sheet.dart'; import 'package:marco/model/dailyTaskPlanning/create_task_botom_sheet.dart';
import 'package:marco/model/dailyTaskPlaning/report_action_widgets.dart'; import 'package:marco/model/dailyTaskPlanning/report_action_widgets.dart';
import 'package:marco/helpers/utils/base_bottom_sheet.dart'; import 'package:marco/helpers/utils/base_bottom_sheet.dart';
class ReportActionBottomSheet extends StatefulWidget { class ReportActionBottomSheet extends StatefulWidget {

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:marco/controller/task_planing/report_task_controller.dart'; import 'package:marco/controller/task_planning/report_task_controller.dart';
import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
import 'package:marco/helpers/widgets/my_button.dart'; import 'package:marco/helpers/widgets/my_button.dart';
import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_spacing.dart';

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:marco/model/dailyTaskPlaning/comment_task_bottom_sheet.dart'; import 'package:marco/model/dailyTaskPlanning/comment_task_bottom_sheet.dart';
import 'package:marco/model/dailyTaskPlaning/report_task_bottom_sheet.dart'; import 'package:marco/model/dailyTaskPlanning/report_task_bottom_sheet.dart';
import 'package:marco/model/dailyTaskPlaning/report_action_bottom_sheet.dart'; import 'package:marco/model/dailyTaskPlanning/report_action_bottom_sheet.dart';
class TaskActionButtons { class TaskActionButtons {
static Widget reportButton({ static Widget reportButton({

View File

@ -0,0 +1,86 @@
import 'dart:convert';
/// Top-level response for the menu API
class MenuResponse {
final bool success; // API call success flag
final String message; // Response message
final List<MenuItem> data; // List of menu items
final dynamic errors; // Error details (if any)
final int statusCode; // HTTP-like status code
final DateTime timestamp; // Response timestamp
MenuResponse({
required this.success,
required this.message,
required this.data,
this.errors,
required this.statusCode,
required this.timestamp,
});
/// Creates a MenuResponse from a JSON map
factory MenuResponse.fromJson(Map<String, dynamic> json) {
return MenuResponse(
success: json['success'] as bool? ?? false,
message: json['message'] as String? ?? '',
data: (json['data'] as List<dynamic>?)
?.map((e) => MenuItem.fromJson(e as Map<String, dynamic>))
.toList() ??
<MenuItem>[],
errors: json['errors'],
statusCode: json['statusCode'] as int? ?? 0,
timestamp: DateTime.tryParse(json['timestamp'] as String? ?? '') ??
DateTime.now(),
);
}
/// Converts the MenuResponse back to JSON map
Map<String, dynamic> toJson() {
return {
'success': success,
'message': message,
'data': data.map((e) => e.toJson()).toList(),
'errors': errors,
'statusCode': statusCode,
'timestamp': timestamp.toIso8601String(),
};
}
/// Parse from raw JSON string
static MenuResponse fromRawJson(String str) =>
MenuResponse.fromJson(json.decode(str) as Map<String, dynamic>);
/// Convert to raw JSON string
String toRawJson() => json.encode(toJson());
}
/// Individual menu item
class MenuItem {
final String id; // Unique item ID
final String name; // Display text
final bool available; // Availability flag
MenuItem({
required this.id,
required this.name,
required this.available,
});
/// Creates MenuItem from JSON map
factory MenuItem.fromJson(Map<String, dynamic> json) {
return MenuItem(
id: json['id'] as String? ?? '',
name: json['name'] as String? ?? '',
available: json['available'] as bool? ?? false,
);
}
/// Converts MenuItem back to JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'available': available,
};
}
}

View File

@ -10,8 +10,8 @@ import 'package:marco/view/error_pages/error_404_screen.dart';
import 'package:marco/view/error_pages/error_500_screen.dart'; import 'package:marco/view/error_pages/error_500_screen.dart';
import 'package:marco/view/dashboard/dashboard_screen.dart'; import 'package:marco/view/dashboard/dashboard_screen.dart';
import 'package:marco/view/dashboard/Attendence/attendance_screen.dart'; import 'package:marco/view/dashboard/Attendence/attendance_screen.dart';
import 'package:marco/view/taskPlaning/daily_task_planing.dart'; import 'package:marco/view/taskPlanning/daily_task_planning.dart';
import 'package:marco/view/taskPlaning/daily_progress.dart'; import 'package:marco/view/taskPlanning/daily_progress.dart';
import 'package:marco/view/employees/employees_screen.dart'; import 'package:marco/view/employees/employees_screen.dart';
import 'package:marco/view/auth/login_option_screen.dart'; import 'package:marco/view/auth/login_option_screen.dart';
import 'package:marco/view/auth/mpin_screen.dart'; import 'package:marco/view/auth/mpin_screen.dart';
@ -55,8 +55,8 @@ getPageRoute() {
middlewares: [AuthMiddleware()]), middlewares: [AuthMiddleware()]),
// Daily Task Planning // Daily Task Planning
GetPage( GetPage(
name: '/dashboard/daily-task-planing', name: '/dashboard/daily-task-Planning',
page: () => DailyTaskPlaningScreen(), page: () => DailyTaskPlanningScreen(),
middlewares: [AuthMiddleware()]), middlewares: [AuthMiddleware()]),
GetPage( GetPage(
name: '/dashboard/daily-task-progress', name: '/dashboard/daily-task-progress',

View File

@ -11,6 +11,8 @@ import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/view/dashboard/dashboard_chart.dart'; import 'package:marco/view/dashboard/dashboard_chart.dart';
import 'package:marco/view/layouts/layout.dart'; import 'package:marco/view/layouts/layout.dart';
import 'package:marco/controller/dynamicMenu/dynamic_menu_controller.dart';
// import 'package:marco/helpers/services/firebase/firebase_messaging_service.dart'; // Commented out // import 'package:marco/helpers/services/firebase/firebase_messaging_service.dart'; // Commented out
class DashboardScreen extends StatefulWidget { class DashboardScreen extends StatefulWidget {
@ -21,7 +23,7 @@ class DashboardScreen extends StatefulWidget {
static const String projectsRoute = "/dashboard"; static const String projectsRoute = "/dashboard";
static const String attendanceRoute = "/dashboard/attendance"; static const String attendanceRoute = "/dashboard/attendance";
static const String tasksRoute = "/dashboard/daily-task"; static const String tasksRoute = "/dashboard/daily-task";
static const String dailyTasksRoute = "/dashboard/daily-task-planing"; static const String dailyTasksRoute = "/dashboard/daily-task-Planning";
static const String dailyTasksProgressRoute = static const String dailyTasksProgressRoute =
"/dashboard/daily-task-progress"; "/dashboard/daily-task-progress";
static const String directoryMainPageRoute = "/dashboard/directory-main-page"; static const String directoryMainPageRoute = "/dashboard/directory-main-page";
@ -33,7 +35,10 @@ class DashboardScreen extends StatefulWidget {
class _DashboardScreenState extends State<DashboardScreen> with UIMixin { class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
final DashboardController dashboardController = final DashboardController dashboardController =
Get.put(DashboardController()); Get.put(DashboardController(), permanent: true);
final DynamicMenuController menuController =
Get.put(DynamicMenuController(), permanent: true);
bool hasMpin = true; bool hasMpin = true;
@override @override
@ -78,31 +83,41 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
); );
} }
/// Dashboard Statistics Section with ProjectController /// Dashboard Statistics Section with ProjectController, Obx reactivity for menus
Widget _buildDashboardStats(BuildContext context) { Widget _buildDashboardStats(BuildContext context) {
return Obx(() {
if (menuController.isLoading.value) {
return _buildLoadingSkeleton(context);
}
if (menuController.hasError.value) {
return Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: MyText.bodySmall(
menuController.errorMessage.value,
color: Colors.red,
),
),
);
}
final stats = [ final stats = [
_StatItem(LucideIcons.scan_face, "Attendance", contentTheme.success, _StatItem(LucideIcons.scan_face, "Attendance", contentTheme.success,
DashboardScreen.attendanceRoute), DashboardScreen.attendanceRoute),
_StatItem(LucideIcons.users, "Employees", contentTheme.warning, _StatItem(LucideIcons.users, "Employees", contentTheme.warning,
DashboardScreen.employeesRoute), DashboardScreen.employeesRoute),
_StatItem(LucideIcons.logs, "Daily Task Planing", contentTheme.info, _StatItem(LucideIcons.logs, "Daily Task Planning", contentTheme.info,
DashboardScreen.dailyTasksRoute), DashboardScreen.dailyTasksRoute),
_StatItem(LucideIcons.list_todo, "Daily Task Progress", contentTheme.info, _StatItem(LucideIcons.list_todo, "Daily Progress Report",
DashboardScreen.dailyTasksProgressRoute), contentTheme.info, DashboardScreen.dailyTasksProgressRoute),
_StatItem(LucideIcons.folder, "Directory", contentTheme.info, _StatItem(LucideIcons.folder, "Directory", contentTheme.info,
DashboardScreen.directoryMainPageRoute), DashboardScreen.directoryMainPageRoute),
_StatItem(LucideIcons.badge_dollar_sign, "Expense", contentTheme.info, _StatItem(LucideIcons.badge_dollar_sign, "Expense", contentTheme.info,
DashboardScreen.expenseMainPageRoute), DashboardScreen.expenseMainPageRoute),
]; ];
return GetBuilder<ProjectController>( final projectController = Get.find<ProjectController>();
id: 'dashboard_controller', final isProjectSelected = projectController.selectedProject != null;
builder: (controller) {
if (controller.isLoading.value) {
return _buildLoadingSkeleton(context);
}
final isProjectSelected = controller.selectedProject != null;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -119,6 +134,16 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
spacing: 10, spacing: 10,
runSpacing: 10, runSpacing: 10,
children: stats children: stats
.where((stat) {
final isAllowed =
menuController.isMenuAllowed(stat.title);
// 👇 Log what is being checked
debugPrint(
"[Dashboard Menu] Checking menu: ${stat.title} -> Allowed: $isAllowed");
return isAllowed;
})
.map((stat) => .map((stat) =>
_buildStatCard(stat, cardWidth, isProjectSelected)) _buildStatCard(stat, cardWidth, isProjectSelected))
.toList(), .toList(),
@ -127,12 +152,20 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
), ),
], ],
); );
}, });
);
} }
/// Attendance Chart Section
/// Attendance Chart Section /// Attendance Chart Section
Widget _buildAttendanceChartSection() { Widget _buildAttendanceChartSection() {
return Obx(() {
final isAttendanceAllowed = menuController.isMenuAllowed("Attendance");
if (!isAttendanceAllowed) {
// 🚫 Don't render anything if attendance menu is not allowed
return const SizedBox.shrink();
}
return GetBuilder<ProjectController>( return GetBuilder<ProjectController>(
id: 'dashboard_controller', id: 'dashboard_controller',
builder: (projectController) { builder: (projectController) {
@ -149,6 +182,7 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
); );
}, },
); );
});
} }
/// No Project Assigned Message /// No Project Assigned Message

View File

@ -124,9 +124,9 @@ class _LeftBarState extends State<LeftBar>
route: '/dashboard/employees'), route: '/dashboard/employees'),
NavigationItem( NavigationItem(
iconData: LucideIcons.logs, iconData: LucideIcons.logs,
title: "Daily Task Planing", title: "Daily Task Planning",
isCondensed: isCondensed, isCondensed: isCondensed,
route: '/dashboard/daily-task-planing'), route: '/dashboard/daily-task-Planning'),
NavigationItem( NavigationItem(
iconData: LucideIcons.list_todo, iconData: LucideIcons.list_todo,
title: "Daily Progress Report", title: "Daily Progress Report",

View File

@ -10,10 +10,10 @@ import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/controller/permission_controller.dart'; import 'package:marco/controller/permission_controller.dart';
import 'package:marco/controller/dashboard/daily_task_controller.dart'; import 'package:marco/controller/dashboard/daily_task_controller.dart';
import 'package:marco/model/dailyTaskPlaning/daily_progress_report_filter.dart'; import 'package:marco/model/dailyTaskPlanning/daily_progress_report_filter.dart';
import 'package:marco/helpers/widgets/avatar.dart'; import 'package:marco/helpers/widgets/avatar.dart';
import 'package:marco/controller/project_controller.dart'; import 'package:marco/controller/project_controller.dart';
import 'package:marco/model/dailyTaskPlaning/task_action_buttons.dart'; import 'package:marco/model/dailyTaskPlanning/task_action_buttons.dart';
import 'package:marco/helpers/widgets/my_custom_skeleton.dart'; import 'package:marco/helpers/widgets/my_custom_skeleton.dart';
import 'package:marco/helpers/utils/permission_constants.dart'; import 'package:marco/helpers/utils/permission_constants.dart';
import 'package:marco/helpers/widgets/my_refresh_indicator.dart'; import 'package:marco/helpers/widgets/my_refresh_indicator.dart';
@ -92,7 +92,7 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
MyText.titleLarge( MyText.titleLarge(
'Daily Task Progress', 'Daily Progress Report',
fontWeight: 700, fontWeight: 700,
color: Colors.black, color: Colors.black,
), ),

View File

@ -6,25 +6,25 @@ import 'package:marco/helpers/widgets/my_card.dart';
import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/controller/permission_controller.dart'; import 'package:marco/controller/permission_controller.dart';
import 'package:marco/controller/task_planing/daily_task_planing_controller.dart'; import 'package:marco/controller/task_Planning/daily_task_Planning_controller.dart';
import 'package:marco/controller/project_controller.dart'; import 'package:marco/controller/project_controller.dart';
import 'package:percent_indicator/percent_indicator.dart'; import 'package:percent_indicator/percent_indicator.dart';
import 'package:marco/model/dailyTaskPlaning/assign_task_bottom_sheet .dart'; import 'package:marco/model/dailyTaskPlanning/assign_task_bottom_sheet .dart';
import 'package:marco/helpers/widgets/my_custom_skeleton.dart'; import 'package:marco/helpers/widgets/my_custom_skeleton.dart';
import 'package:marco/helpers/utils/permission_constants.dart'; import 'package:marco/helpers/utils/permission_constants.dart';
import 'package:marco/helpers/widgets/my_refresh_indicator.dart'; import 'package:marco/helpers/widgets/my_refresh_indicator.dart';
class DailyTaskPlaningScreen extends StatefulWidget { class DailyTaskPlanningScreen extends StatefulWidget {
DailyTaskPlaningScreen({super.key}); DailyTaskPlanningScreen({super.key});
@override @override
State<DailyTaskPlaningScreen> createState() => _DailyTaskPlaningScreenState(); State<DailyTaskPlanningScreen> createState() => _DailyTaskPlanningScreenState();
} }
class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen> class _DailyTaskPlanningScreenState extends State<DailyTaskPlanningScreen>
with UIMixin { with UIMixin {
final DailyTaskPlaningController dailyTaskPlaningController = final DailyTaskPlanningController dailyTaskPlanningController =
Get.put(DailyTaskPlaningController()); Get.put(DailyTaskPlanningController());
final PermissionController permissionController = final PermissionController permissionController =
Get.put(PermissionController()); Get.put(PermissionController());
final ProjectController projectController = Get.find<ProjectController>(); final ProjectController projectController = Get.find<ProjectController>();
@ -36,7 +36,7 @@ class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen>
// Initial fetch if a project is already selected // Initial fetch if a project is already selected
final projectId = projectController.selectedProjectId.value; final projectId = projectController.selectedProjectId.value;
if (projectId.isNotEmpty) { if (projectId.isNotEmpty) {
dailyTaskPlaningController.fetchTaskData(projectId); dailyTaskPlanningController.fetchTaskData(projectId);
} }
// Reactive fetch on project ID change // Reactive fetch on project ID change
@ -44,7 +44,7 @@ class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen>
projectController.selectedProjectId, projectController.selectedProjectId,
(newProjectId) { (newProjectId) {
if (newProjectId.isNotEmpty) { if (newProjectId.isNotEmpty) {
dailyTaskPlaningController.fetchTaskData(newProjectId); dailyTaskPlanningController.fetchTaskData(newProjectId);
} }
}, },
); );
@ -77,7 +77,7 @@ class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen>
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
MyText.titleLarge( MyText.titleLarge(
'Daily Task Planing', 'Daily Task Planning',
fontWeight: 700, fontWeight: 700,
color: Colors.black, color: Colors.black,
), ),
@ -118,7 +118,7 @@ class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen>
final projectId = projectController.selectedProjectId.value; final projectId = projectController.selectedProjectId.value;
if (projectId.isNotEmpty) { if (projectId.isNotEmpty) {
try { try {
await dailyTaskPlaningController.fetchTaskData(projectId); await dailyTaskPlanningController.fetchTaskData(projectId);
} catch (e) { } catch (e) {
debugPrint('Error refreshing task data: ${e.toString()}'); debugPrint('Error refreshing task data: ${e.toString()}');
} }
@ -135,9 +135,9 @@ class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen>
kToolbarHeight - kToolbarHeight -
MediaQuery.of(context).padding.top, MediaQuery.of(context).padding.top,
), ),
child: GetBuilder<DailyTaskPlaningController>( child: GetBuilder<DailyTaskPlanningController>(
init: dailyTaskPlaningController, init: dailyTaskPlanningController,
tag: 'daily_task_planing_controller', tag: 'daily_task_Planning_controller',
builder: (controller) { builder: (controller) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -160,8 +160,8 @@ class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen>
Widget dailyProgressReportTab() { Widget dailyProgressReportTab() {
return Obx(() { return Obx(() {
final isLoading = dailyTaskPlaningController.isLoading.value; final isLoading = dailyTaskPlanningController.isLoading.value;
final dailyTasks = dailyTaskPlaningController.dailyTasks; final dailyTasks = dailyTaskPlanningController.dailyTasks;
if (isLoading) { if (isLoading) {
return SkeletonLoaders.dailyProgressPlanningSkeletonCollapsedOnly(); return SkeletonLoaders.dailyProgressPlanningSkeletonCollapsedOnly();