feat: Implement pagination and service filtering in daily task fetching logic
This commit is contained in:
parent
fc081c779e
commit
7d211e24f8
@ -24,8 +24,12 @@ class DailyTaskController extends GetxController {
|
||||
}
|
||||
|
||||
RxBool isLoading = true.obs;
|
||||
RxBool isLoadingMore = false.obs;
|
||||
Map<String, List<TaskModel>> groupedDailyTasks = {};
|
||||
|
||||
// Pagination
|
||||
int currentPage = 1;
|
||||
int pageSize = 20;
|
||||
bool hasMore = true;
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
@ -47,48 +51,49 @@ class DailyTaskController extends GetxController {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> fetchTaskData(String? projectId) async {
|
||||
if (projectId == null) {
|
||||
logSafe("fetchTaskData: Skipped, projectId is null",
|
||||
level: LogLevel.warning);
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> fetchTaskData(
|
||||
String projectId, {
|
||||
List<String>? serviceIds,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 20,
|
||||
bool isLoadMore = false,
|
||||
}) async {
|
||||
if (!isLoadMore) {
|
||||
isLoading.value = true;
|
||||
currentPage = 1;
|
||||
hasMore = true;
|
||||
groupedDailyTasks.clear();
|
||||
dailyTasks.clear();
|
||||
} else {
|
||||
isLoadingMore.value = true;
|
||||
}
|
||||
|
||||
final response = await ApiService.getDailyTasks(
|
||||
projectId,
|
||||
dateFrom: startDateTask,
|
||||
dateTo: endDateTask,
|
||||
serviceIds: serviceIds,
|
||||
pageNumber: pageNumber,
|
||||
pageSize: pageSize,
|
||||
);
|
||||
|
||||
isLoading.value = false;
|
||||
|
||||
if (response != null) {
|
||||
groupedDailyTasks.clear();
|
||||
|
||||
if (response != null && response.isNotEmpty) {
|
||||
for (var taskJson in response) {
|
||||
final task = TaskModel.fromJson(taskJson);
|
||||
final assignmentDateKey =
|
||||
task.assignmentDate.toIso8601String().split('T')[0];
|
||||
|
||||
groupedDailyTasks.putIfAbsent(assignmentDateKey, () => []).add(task);
|
||||
}
|
||||
|
||||
dailyTasks = groupedDailyTasks.values.expand((list) => list).toList();
|
||||
currentPage = pageNumber;
|
||||
} else {
|
||||
hasMore = false;
|
||||
}
|
||||
|
||||
logSafe(
|
||||
"Daily tasks fetched and grouped: ${dailyTasks.length} for project $projectId",
|
||||
level: LogLevel.info,
|
||||
);
|
||||
isLoading.value = false;
|
||||
isLoadingMore.value = false;
|
||||
|
||||
update();
|
||||
} else {
|
||||
logSafe(
|
||||
"Failed to fetch daily tasks for project $projectId",
|
||||
level: LogLevel.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> selectDateRangeForTaskData(
|
||||
@ -119,7 +124,14 @@ class DailyTaskController extends GetxController {
|
||||
level: LogLevel.info,
|
||||
);
|
||||
|
||||
await controller.fetchTaskData(controller.selectedProjectId);
|
||||
// ✅ Add null check before calling fetchTaskData
|
||||
final projectId = controller.selectedProjectId;
|
||||
if (projectId != null && projectId.isNotEmpty) {
|
||||
await controller.fetchTaskData(projectId);
|
||||
} else {
|
||||
logSafe("Project ID is null or empty, skipping fetchTaskData",
|
||||
level: LogLevel.warning);
|
||||
}
|
||||
}
|
||||
|
||||
void refreshTasksFromNotification({
|
||||
@ -131,5 +143,4 @@ void refreshTasksFromNotification({
|
||||
|
||||
update(); // rebuilds UI
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2026,18 +2026,32 @@ class ApiService {
|
||||
String projectId, {
|
||||
DateTime? dateFrom,
|
||||
DateTime? dateTo,
|
||||
List<String>? serviceIds,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 20,
|
||||
}) async {
|
||||
final filterBody = {
|
||||
"serviceIds": serviceIds ?? [],
|
||||
};
|
||||
|
||||
final query = {
|
||||
"projectId": projectId,
|
||||
"pageNumber": pageNumber.toString(),
|
||||
"pageSize": pageSize.toString(),
|
||||
if (dateFrom != null)
|
||||
"dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
|
||||
if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo),
|
||||
"filter": jsonEncode(filterBody),
|
||||
};
|
||||
return _getRequest(ApiEndpoints.getDailyTask, queryParams: query).then(
|
||||
(res) =>
|
||||
res != null ? _parseResponse(res, label: 'Daily Tasks') : null);
|
||||
|
||||
final uri = Uri.parse(ApiEndpoints.getDailyTask).replace(queryParameters: query);
|
||||
|
||||
final response = await _getRequest(uri.toString());
|
||||
|
||||
return response != null ? _parseResponse(response, label: 'Daily Tasks') : null;
|
||||
}
|
||||
|
||||
|
||||
static Future<bool> reportTask({
|
||||
required String id,
|
||||
required int completedTask,
|
||||
|
@ -25,17 +25,15 @@ class OrganizationSelector extends StatelessWidget {
|
||||
required List<String> items,
|
||||
}) {
|
||||
return PopupMenuButton<String>(
|
||||
color: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
onSelected: (name) async {
|
||||
// Determine the selected organization
|
||||
Organization? org = name == "All Organizations"
|
||||
? null
|
||||
: controller.organizations.firstWhere((e) => e.name == name);
|
||||
|
||||
// Update controller state
|
||||
controller.selectOrganization(org);
|
||||
|
||||
// Trigger callback for post-selection logic
|
||||
if (onSelectionChanged != null) {
|
||||
await onSelectionChanged!(org);
|
||||
}
|
||||
@ -45,9 +43,10 @@ class OrganizationSelector extends StatelessWidget {
|
||||
.toList(),
|
||||
child: Container(
|
||||
height: height,
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
color:
|
||||
Colors.white,
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
|
@ -44,28 +44,50 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
|
||||
Get.find<PermissionController>();
|
||||
final ProjectController projectController = Get.find<ProjectController>();
|
||||
final ServiceController serviceController = Get.put(ServiceController());
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_scrollController.addListener(() {
|
||||
if (_scrollController.position.pixels >=
|
||||
_scrollController.position.maxScrollExtent - 100 &&
|
||||
dailyTaskController.hasMore &&
|
||||
!dailyTaskController.isLoadingMore.value) {
|
||||
final projectId = dailyTaskController.selectedProjectId;
|
||||
if (projectId != null && projectId.isNotEmpty) {
|
||||
dailyTaskController.fetchTaskData(
|
||||
projectId,
|
||||
pageNumber: dailyTaskController.currentPage + 1,
|
||||
pageSize: dailyTaskController.pageSize,
|
||||
isLoadMore: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
final initialProjectId = projectController.selectedProjectId.value;
|
||||
if (initialProjectId.isNotEmpty) {
|
||||
dailyTaskController.selectedProjectId = initialProjectId;
|
||||
dailyTaskController.fetchTaskData(initialProjectId);
|
||||
serviceController.fetchServices(initialProjectId);
|
||||
}
|
||||
|
||||
ever<String>(
|
||||
projectController.selectedProjectId,
|
||||
(newProjectId) async {
|
||||
// Update when project changes
|
||||
ever<String>(projectController.selectedProjectId, (newProjectId) async {
|
||||
if (newProjectId.isNotEmpty &&
|
||||
newProjectId != dailyTaskController.selectedProjectId) {
|
||||
dailyTaskController.selectedProjectId = newProjectId;
|
||||
await dailyTaskController.fetchTaskData(newProjectId);
|
||||
await serviceController.fetchServices(newProjectId);
|
||||
dailyTaskController.update(['daily_progress_report_controller']);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -158,7 +180,10 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
|
||||
if (projectId?.isNotEmpty ?? false) {
|
||||
await dailyTaskController.fetchTaskData(
|
||||
projectId!,
|
||||
// serviceId: service?.id,
|
||||
serviceIds:
|
||||
service != null ? [service.id] : null,
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -321,10 +346,12 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
|
||||
final isLoading = dailyTaskController.isLoading.value;
|
||||
final groupedTasks = dailyTaskController.groupedDailyTasks;
|
||||
|
||||
if (isLoading) {
|
||||
// Initial loading skeleton
|
||||
if (isLoading && dailyTaskController.currentPage == 1) {
|
||||
return SkeletonLoaders.dailyProgressReportSkeletonLoader();
|
||||
}
|
||||
|
||||
// No tasks
|
||||
if (groupedTasks.isEmpty) {
|
||||
return Center(
|
||||
child: MyText.bodySmall(
|
||||
@ -337,23 +364,33 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
|
||||
final sortedDates = groupedTasks.keys.toList()
|
||||
..sort((a, b) => b.compareTo(a));
|
||||
|
||||
// If only one date, make it expanded by default
|
||||
if (sortedDates.length == 1 &&
|
||||
!dailyTaskController.expandedDates.contains(sortedDates[0])) {
|
||||
dailyTaskController.expandedDates.add(sortedDates[0]);
|
||||
}
|
||||
|
||||
return MyCard.bordered(
|
||||
borderRadiusAll: 10,
|
||||
border: Border.all(color: Colors.grey.withOpacity(0.2)),
|
||||
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
|
||||
paddingAll: 8,
|
||||
child: ListView.separated(
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: sortedDates.length,
|
||||
separatorBuilder: (_, __) => Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
Divider(color: Colors.grey.withOpacity(0.3), thickness: 1),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount: sortedDates.length + 1, // +1 for loading indicator
|
||||
itemBuilder: (context, dateIndex) {
|
||||
// Bottom loading indicator
|
||||
if (dateIndex == sortedDates.length) {
|
||||
return Obx(() => dailyTaskController.isLoadingMore.value
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
)
|
||||
: const SizedBox.shrink());
|
||||
}
|
||||
|
||||
final dateKey = sortedDates[dateIndex];
|
||||
final tasksForDate = groupedTasks[dateKey]!;
|
||||
final date = DateTime.tryParse(dateKey);
|
||||
@ -389,7 +426,6 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
|
||||
return Column(
|
||||
children: tasksForDate.asMap().entries.map((entry) {
|
||||
final task = entry.value;
|
||||
final index = entry.key;
|
||||
|
||||
final activityName =
|
||||
task.workItem?.activityMaster?.activityName ?? 'N/A';
|
||||
@ -407,20 +443,17 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
|
||||
? (completed / planned).clamp(0.0, 1.0)
|
||||
: 0.0;
|
||||
final parentTaskID = task.id;
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: MyContainer(
|
||||
paddingAll: 12,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.bodyMedium(activityName,
|
||||
fontWeight: 600),
|
||||
MyText.bodyMedium(activityName, fontWeight: 600),
|
||||
const SizedBox(height: 2),
|
||||
MyText.bodySmall(location,
|
||||
color: Colors.grey),
|
||||
MyText.bodySmall(location, color: Colors.grey),
|
||||
const SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
onTap: () => _showTeamMembersBottomSheet(
|
||||
@ -449,8 +482,7 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius:
|
||||
BorderRadius.circular(6),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
FractionallySizedBox(
|
||||
@ -463,8 +495,7 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
|
||||
: progress >= 0.5
|
||||
? Colors.amber
|
||||
: Colors.red,
|
||||
borderRadius:
|
||||
BorderRadius.circular(6),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -491,8 +522,7 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
|
||||
.toString()
|
||||
.isEmpty) &&
|
||||
permissionController.hasPermission(
|
||||
Permissions
|
||||
.assignReportTask)) ...[
|
||||
Permissions.assignReportTask)) ...[
|
||||
TaskActionButtons.reportButton(
|
||||
context: context,
|
||||
task: task,
|
||||
@ -528,13 +558,6 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (index != tasksForDate.length - 1)
|
||||
Divider(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
thickness: 1,
|
||||
height: 1),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
|
@ -37,18 +37,19 @@ class _DailyTaskPlanningScreenState extends State<DailyTaskPlanningScreen>
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Initial fetch if a project is already selected
|
||||
final projectId = projectController.selectedProjectId.value;
|
||||
if (projectId.isNotEmpty) {
|
||||
dailyTaskPlanningController.fetchTaskData(projectId);
|
||||
serviceController.fetchServices(projectId); // <-- Fetch services here
|
||||
}
|
||||
|
||||
// Reactive fetch on project ID change
|
||||
ever<String>(
|
||||
projectController.selectedProjectId,
|
||||
(newProjectId) {
|
||||
if (newProjectId.isNotEmpty) {
|
||||
dailyTaskPlanningController.fetchTaskData(newProjectId);
|
||||
serviceController
|
||||
.fetchServices(newProjectId);
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -150,8 +151,7 @@ class _DailyTaskPlanningScreenState extends State<DailyTaskPlanningScreen>
|
||||
Padding(
|
||||
padding: MySpacing.x(10),
|
||||
child: ServiceSelector(
|
||||
controller:
|
||||
serviceController,
|
||||
controller: serviceController,
|
||||
height: 40,
|
||||
onSelectionChanged: (service) async {
|
||||
final projectId =
|
||||
|
Loading…
x
Reference in New Issue
Block a user