feat: Enhance project selection handling across various screens and controllers

This commit is contained in:
Vaibhav Surve 2025-06-11 21:55:15 +05:30
parent 936faae07d
commit b81ac33b2d
7 changed files with 135 additions and 267 deletions

View File

@ -32,17 +32,18 @@ class ProjectController extends GetxController {
if (response != null && response.isNotEmpty) {
projects.assignAll(
response.map((json) => ProjectModel.fromJson(json)).toList());
response.map((json) => ProjectModel.fromJson(json)).toList());
String? savedId = LocalStorage.getString('selectedProjectId');
if (savedId != null && projects.any((p) => p.id == savedId)) {
selectedProjectId = RxString(savedId);
} else {
selectedProjectId = RxString(projects.first.id.toString());
LocalStorage.saveString('selectedProjectId', projects.first.id.toString());
LocalStorage.saveString(
'selectedProjectId', projects.first.id.toString());
}
isProjectSelectionExpanded.value = false;
isProjectSelectionExpanded.value = false;
log.i("Projects fetched: ${projects.length}");
} else {
log.w("No projects found or API call failed.");
@ -53,8 +54,9 @@ class ProjectController extends GetxController {
update(['dashboard_controller']);
}
void updateSelectedProject(String projectId) {
Future<void> updateSelectedProject(String projectId) async {
selectedProjectId?.value = projectId;
LocalStorage.saveString('selectedProjectId', projectId);
await LocalStorage.saveString('selectedProjectId', projectId);
update();
}
}

View File

@ -24,14 +24,11 @@ class AttendanceFilterBottomSheet extends StatefulWidget {
class _AttendanceFilterBottomSheetState
extends State<AttendanceFilterBottomSheet> {
late String? tempSelectedProjectId;
late String tempSelectedTab;
bool showProjectList = false;
@override
void initState() {
super.initState();
tempSelectedProjectId = widget.controller.selectedProjectId;
tempSelectedTab = widget.selectedTab;
}
@ -46,55 +43,7 @@ class _AttendanceFilterBottomSheetState
return "Date Range";
}
List<Widget> buildProjectList() {
final accessibleProjects = widget.controller.projects
.where((project) =>
widget.permissionController.isUserAssignedToProject(
project.id.toString()))
.toList();
if (accessibleProjects.isEmpty) {
return [
const Padding(
padding: EdgeInsets.all(12.0),
child: Center(child: Text('No Projects Assigned')),
),
];
}
return accessibleProjects.map((project) {
final isSelected = tempSelectedProjectId == project.id.toString();
return ListTile(
dense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
title: Text(project.name),
trailing: isSelected ? const Icon(Icons.check) : null,
onTap: () {
setState(() {
tempSelectedProjectId = project.id.toString();
showProjectList = false;
});
},
);
}).toList();
}
List<Widget> buildMainFilters() {
final accessibleProjects = widget.controller.projects
.where((project) =>
widget.permissionController.isUserAssignedToProject(
project.id.toString()))
.toList();
final selectedProject = accessibleProjects.isNotEmpty
? accessibleProjects.firstWhere(
(p) => p.id.toString() == tempSelectedProjectId,
orElse: () => accessibleProjects[0],
)
: null;
final selectedProjectName = selectedProject?.name ?? "Select Project";
final hasRegularizationPermission = widget.permissionController
.hasPermission(Permissions.regularizeAttendance);
@ -112,24 +61,6 @@ class _AttendanceFilterBottomSheetState
}).toList();
List<Widget> widgets = [
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
child: Align(
alignment: Alignment.centerLeft,
child: MyText.titleSmall(
"Project",
fontWeight: 600,
),
),
),
ListTile(
dense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
title: Text(selectedProjectName),
trailing: const Icon(Icons.arrow_drop_down),
onTap: () => setState(() => showProjectList = true),
),
const Divider(),
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
child: Align(
@ -216,7 +147,6 @@ class _AttendanceFilterBottomSheetState
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Drag handle
Padding(
padding: const EdgeInsets.only(top: 12, bottom: 8),
child: Center(
@ -230,7 +160,7 @@ class _AttendanceFilterBottomSheetState
),
),
),
if (showProjectList) ...buildProjectList() else ...buildMainFilters(),
...buildMainFilters(),
const Divider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
@ -247,7 +177,6 @@ class _AttendanceFilterBottomSheetState
child: const Text('Apply Filter'),
onPressed: () {
Navigator.pop(context, {
'projectId': tempSelectedProjectId,
'selectedTab': tempSelectedTab,
});
},

View File

@ -20,13 +20,9 @@ class DailyProgressReportFilter extends StatefulWidget {
}
class _DailyProgressReportFilterState extends State<DailyProgressReportFilter> {
late String? tempSelectedProjectId;
bool showProjectList = false;
@override
void initState() {
super.initState();
tempSelectedProjectId = widget.controller.selectedProjectId;
}
String getLabelText() {
@ -42,116 +38,6 @@ class _DailyProgressReportFilterState extends State<DailyProgressReportFilter> {
@override
Widget build(BuildContext context) {
final accessibleProjects = widget.controller.projects
.where((project) => widget.permissionController
.isUserAssignedToProject(project.id.toString()))
.toList();
List<Widget> filterWidgets;
if (showProjectList) {
filterWidgets = accessibleProjects.isEmpty
? [
const Padding(
padding: EdgeInsets.all(12.0),
child: Center(child: Text('No Projects Assigned')),
),
]
: accessibleProjects.map((project) {
final isSelected = tempSelectedProjectId == project.id.toString();
return ListTile(
dense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
title: Text(project.name),
trailing: isSelected ? const Icon(Icons.check) : null,
onTap: () {
setState(() {
tempSelectedProjectId = project.id.toString();
showProjectList = false;
});
},
);
}).toList();
} else {
final selectedProject = accessibleProjects.isNotEmpty
? accessibleProjects.firstWhere(
(p) => p.id.toString() == tempSelectedProjectId,
orElse: () => accessibleProjects[0],
)
: null;
final selectedProjectName = selectedProject?.name ?? "Select Project";
filterWidgets = [
Padding(
padding: EdgeInsets.fromLTRB(16, 12, 16, 4),
child: Align(
alignment: Alignment.centerLeft,
child: MyText.titleSmall(
'Select Project',
fontWeight: 600,
),
),
),
ListTile(
dense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
title: Text(selectedProjectName),
trailing: const Icon(Icons.arrow_drop_down),
onTap: () => setState(() => showProjectList = true),
),
];
filterWidgets.addAll([
const Divider(),
Padding(
padding: EdgeInsets.fromLTRB(16, 12, 16, 4),
child: Align(
alignment: Alignment.centerLeft,
child: MyText.titleSmall(
"Select Date Range",
fontWeight: 600,
)),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: InkWell(
borderRadius: BorderRadius.circular(10),
onTap: () => widget.controller.selectDateRangeForTaskData(
context,
widget.controller,
),
child: Ink(
decoration: BoxDecoration(
color: Colors.grey.shade100,
border: Border.all(color: Colors.grey.shade400),
borderRadius: BorderRadius.circular(10),
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Row(
children: [
Icon(Icons.date_range, color: Colors.blue.shade600),
const SizedBox(width: 12),
Expanded(
child: Text(
getLabelText(),
style: const TextStyle(
fontSize: 16,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
),
),
const Icon(Icons.arrow_drop_down, color: Colors.grey),
],
),
),
),
),
]);
}
return SafeArea(
child: Padding(
padding: EdgeInsets.only(
@ -174,7 +60,55 @@ class _DailyProgressReportFilterState extends State<DailyProgressReportFilter> {
),
),
),
...filterWidgets,
const Divider(),
Padding(
padding: EdgeInsets.fromLTRB(16, 12, 16, 4),
child: Align(
alignment: Alignment.centerLeft,
child: MyText.titleSmall(
"Select Date Range",
fontWeight: 600,
),
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: InkWell(
borderRadius: BorderRadius.circular(10),
onTap: () => widget.controller.selectDateRangeForTaskData(
context,
widget.controller,
),
child: Ink(
decoration: BoxDecoration(
color: Colors.grey.shade100,
border: Border.all(color: Colors.grey.shade400),
borderRadius: BorderRadius.circular(10),
),
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 14),
child: Row(
children: [
Icon(Icons.date_range, color: Colors.blue.shade600),
const SizedBox(width: 12),
Expanded(
child: Text(
getLabelText(),
style: const TextStyle(
fontSize: 16,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
),
),
const Icon(Icons.arrow_drop_down, color: Colors.grey),
],
),
),
),
),
const Divider(),
Padding(
padding:
@ -190,9 +124,7 @@ class _DailyProgressReportFilterState extends State<DailyProgressReportFilter> {
),
child: const Text('Apply Filter'),
onPressed: () {
Navigator.pop(context, {
'projectId': tempSelectedProjectId,
});
Navigator.pop(context, {});
},
),
),

View File

@ -20,6 +20,7 @@ import 'package:marco/model/attendance/log_details_view.dart';
import 'package:marco/model/attendance/attendence_action_button.dart';
import 'package:marco/model/attendance/regualrize_action_button.dart';
import 'package:marco/model/attendance/attendence_filter_sheet.dart';
import 'package:marco/controller/project_controller.dart'; // adjust if needed
class AttendanceScreen extends StatefulWidget {
AttendanceScreen({super.key});
@ -35,6 +36,24 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
Get.put(PermissionController());
String selectedTab = 'todaysAttendance';
@override
void initState() {
super.initState();
final projectController = Get.find<ProjectController>();
ever<String?>(projectController.selectedProjectId!, (projectId) async {
if (projectId != null && projectId.isNotEmpty) {
try {
await attendanceController.fetchEmployeesByProject(projectId);
await attendanceController.fetchAttendanceLogs(projectId);
await attendanceController.fetchRegularizationLogs(projectId);
await attendanceController.fetchProjectData(projectId);
attendanceController.update(['attendance_dashboard_controller']);
} catch (e) {
debugPrint("Error updating data on project change: $e");
}
}
});
}
@override
Widget build(BuildContext context) {
@ -93,7 +112,10 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
if (result != null) {
final selectedProjectId =
result['projectId'] as String?;
Get.find<ProjectController>()
.selectedProjectId
?.value;
final selectedView = result['selectedTab'] as String?;
if (selectedProjectId != null &&
@ -146,8 +168,10 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
child: InkWell(
borderRadius: BorderRadius.circular(24),
onTap: () async {
final projectId =
attendanceController.selectedProjectId;
final projectId = Get.find<ProjectController>()
.selectedProjectId
?.value;
if (projectId != null && projectId.isNotEmpty) {
try {
await attendanceController
@ -181,7 +205,7 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
],
),
Padding(
padding: MySpacing.x(flexSpacing / 2),
padding: MySpacing.x(0),
child: MyFlex(children: [
MyFlexItem(
sizes: 'lg-12 md-12 sm-12',

View File

@ -51,7 +51,8 @@ class _LayoutState extends State<Layout> {
Expanded(
child: SingleChildScrollView(
key: controller.scrollKey,
padding: EdgeInsets.all(isMobile ? 16 : 32),
padding: EdgeInsets.symmetric(horizontal: 0, vertical: isMobile ? 16 : 32),
child: widget.child,
),
),

View File

@ -17,6 +17,7 @@ import 'package:marco/model/dailyTaskPlaning/daily_progress_report_filter.dart';
import 'package:marco/helpers/widgets/avatar.dart';
import 'package:marco/model/dailyTaskPlaning/comment_task_bottom_sheet.dart';
import 'package:marco/model/dailyTaskPlaning/report_task_bottom_sheet.dart';
import 'package:marco/controller/project_controller.dart';
class DailyProgressReportScreen extends StatefulWidget {
const DailyProgressReportScreen({super.key});
@ -40,6 +41,30 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
Get.put(DailyTaskController());
final PermissionController permissionController =
Get.put(PermissionController());
final ProjectController projectController = Get.find<ProjectController>();
@override
void initState() {
super.initState();
final initialProjectId = projectController.selectedProjectId?.value;
if (initialProjectId != null) {
dailyTaskController.selectedProjectId = initialProjectId;
dailyTaskController.fetchTaskData(initialProjectId);
}
ever<String?>(
projectController.selectedProjectId!,
(newProjectId) async {
if (newProjectId != null &&
newProjectId != dailyTaskController.selectedProjectId) {
dailyTaskController.selectedProjectId = newProjectId;
await dailyTaskController.fetchTaskData(newProjectId);
dailyTaskController.update(['daily_progress_report_controller']);
}
},
);
}
@override
Widget build(BuildContext context) {

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/view/layouts/layout.dart';
import 'package:marco/controller/permission_controller.dart';
import 'package:marco/model/dailyTaskPlaning/daily_task_planing_filter.dart';
import 'package:marco/controller/task_planing/daily_task_planing_controller.dart';
import 'package:marco/model/dailyTaskPlaning/assign_task_bottom_sheet .dart';
import 'package:marco/controller/project_controller.dart';
import 'package:percent_indicator/percent_indicator.dart';
import 'package:marco/model/dailyTaskPlaning/assign_task_bottom_sheet .dart';
class DailyTaskPlaningScreen extends StatefulWidget {
DailyTaskPlaningScreen({super.key});
@ -28,6 +28,25 @@ class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen>
Get.put(DailyTaskPlaningController());
final PermissionController permissionController =
Get.put(PermissionController());
final ProjectController projectController = Get.find<ProjectController>();
@override
void initState() {
super.initState();
// Initial fetch
final projectId = projectController.selectedProjectId?.value;
if (projectId != null) {
dailyTaskPlaningController.fetchTaskData(projectId);
}
// Reactive fetch on project ID change
ever<String?>(projectController.selectedProjectId!, (projectId) {
if (projectId != null) {
dailyTaskPlaningController.fetchTaskData(projectId);
}
});
}
@override
Widget build(BuildContext context) {
@ -62,70 +81,6 @@ class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen>
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
MyText.bodyMedium(
"Filter",
fontWeight: 600,
),
Tooltip(
message: 'Project',
child: InkWell(
borderRadius: BorderRadius.circular(24),
onTap: () async {
final result =
await showModalBottomSheet<Map<String, dynamic>>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12)),
),
builder: (context) => DailyTaskPlaningFilter(
controller: dailyTaskPlaningController,
permissionController: permissionController,
),
);
if (result != null) {
final selectedProjectId =
result['projectId'] as String?;
if (selectedProjectId != null &&
selectedProjectId !=
dailyTaskPlaningController
.selectedProjectId) {
// Update the controller's selected project ID
dailyTaskPlaningController.selectedProjectId =
selectedProjectId;
try {
// Fetch tasks for the new project
await dailyTaskPlaningController
.fetchTaskData(selectedProjectId);
} catch (e) {
debugPrint(
'Error fetching task data: ${e.toString()}');
}
// Update the UI
dailyTaskPlaningController
.update(['daily_task_planing_controller']);
}
}
},
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
Icons.filter_list_alt,
color: Colors.blueAccent,
size: 28,
),
),
),
),
),
const SizedBox(width: 8),
MyText.bodyMedium(
"Refresh",
@ -137,7 +92,7 @@ class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen>
borderRadius: BorderRadius.circular(24),
onTap: () async {
final projectId =
dailyTaskPlaningController.selectedProjectId;
projectController.selectedProjectId?.value;
if (projectId != null) {
try {
await dailyTaskPlaningController