feat: Enhance project selection handling across various screens and controllers
This commit is contained in:
parent
936faae07d
commit
b81ac33b2d
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
},
|
||||
|
@ -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, {});
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -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',
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user