marco.pms.mobileapp/lib/view/taskPlaning/daily_task_planing.dart
Vaibhav Surve 34100a4d9e -- Enhance layout with floating action button and navigation improvements
- Added a floating action button to the Layout widget for better accessibility.
- Updated the left bar navigation items for clarity and consistency.
- Introduced Daily Progress Report and Daily Task Planning screens with comprehensive UI.
- Implemented filtering and refreshing functionalities in task planning.
- Improved user experience with better spacing and layout adjustments.
- Updated pubspec.yaml to include new dependencies for image handling and path management.
2025-05-28 17:35:42 +05:30

486 lines
21 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:marco/helpers/theme/app_theme.dart';
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
import 'package:marco/helpers/utils/my_shadow.dart';
import 'package:marco/helpers/widgets/my_breadcrumb.dart';
import 'package:marco/helpers/widgets/my_breadcrumb_item.dart';
import 'package:marco/helpers/widgets/my_card.dart';
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';
class DailyTaskPlaningScreen extends StatefulWidget {
DailyTaskPlaningScreen({super.key});
@override
State<DailyTaskPlaningScreen> createState() => _DailyTaskPlaningScreenState();
}
class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen>
with UIMixin {
final DailyTaskPlaningController dailyTaskPlaningController =
Get.put(DailyTaskPlaningController());
final PermissionController permissionController =
Get.put(PermissionController());
@override
Widget build(BuildContext context) {
return Layout(
child: GetBuilder<DailyTaskPlaningController>(
init: dailyTaskPlaningController,
tag: 'daily_task_planing_controller',
builder: (controller) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: MySpacing.x(flexSpacing),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MyText.titleMedium("Daily Task Planning",
fontSize: 18, fontWeight: 600),
MyBreadcrumb(
children: [
MyBreadcrumbItem(name: 'Dashboard'),
MyBreadcrumbItem(
name: 'Daily Task Planning', active: true),
],
),
],
),
),
MySpacing.height(flexSpacing),
Padding(
padding: MySpacing.x(flexSpacing),
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",
fontWeight: 600,
),
Tooltip(
message: 'Refresh Data',
child: InkWell(
borderRadius: BorderRadius.circular(24),
onTap: () async {
final projectId =
dailyTaskPlaningController.selectedProjectId;
if (projectId != null) {
try {
await dailyTaskPlaningController
.fetchTaskData(projectId);
} catch (e) {
debugPrint(
'Error refreshing task data: ${e.toString()}');
}
}
},
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
Icons.refresh,
color: Colors.green,
size: 28,
),
),
),
),
),
],
),
),
Padding(
padding: MySpacing.x(flexSpacing),
child: dailyProgressReportTab(),
),
],
);
},
),
);
}
Widget dailyProgressReportTab() {
return Obx(() {
final isLoading = dailyTaskPlaningController.isLoading.value;
final dailyTasks = dailyTaskPlaningController.dailyTasks;
if (isLoading) {
return Center(child: CircularProgressIndicator());
}
if (dailyTasks.isEmpty) {
return Center(
child: MyText.bodySmall(
"No Progress Report Found",
fontWeight: 600,
),
);
}
final buildingExpansionState = <String, bool>{};
final floorExpansionState = <String, bool>{};
Widget buildExpandIcon(bool isExpanded) {
return Container(
width: 32,
height: 32,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.shade200,
),
child: Icon(
isExpanded ? Icons.remove : Icons.add,
size: 20,
color: Colors.black87,
),
);
}
return StatefulBuilder(builder: (context, setMainState) {
final filteredBuildings = dailyTasks.expand((task) {
return task.buildings.where((building) {
return building.floors.any((floor) =>
floor.workAreas.any((area) => area.workItems.isNotEmpty));
});
}).toList();
if (filteredBuildings.isEmpty) {
return Center(
child: MyText.bodySmall(
"No Progress Report Found",
fontWeight: 600,
),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: filteredBuildings.map((building) {
final buildingKey = building.id.toString();
return MyCard.bordered(
borderRadiusAll: 12,
paddingAll: 0,
margin: MySpacing.bottom(12),
shadow: MyShadow(elevation: 3),
child: Theme(
data: Theme.of(context)
.copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
onExpansionChanged: (expanded) {
setMainState(() {
buildingExpansionState[buildingKey] = expanded;
});
},
trailing: buildExpandIcon(
buildingExpansionState[buildingKey] ?? false),
tilePadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 0),
collapsedShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
leading: Container(
decoration: BoxDecoration(
color: Colors.blueAccent.withOpacity(0.1),
shape: BoxShape.circle,
),
padding: const EdgeInsets.all(8),
child: Icon(
Icons.location_city_rounded,
color: Colors.blueAccent,
size: 24,
),
),
title: MyText.titleMedium(
building.name,
fontWeight: 700,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
childrenPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 0),
children: building.floors.expand((floor) {
final validWorkAreas = floor.workAreas
.where((area) => area.workItems.isNotEmpty);
// For each valid work area, return a Floor+WorkArea ExpansionTile
return validWorkAreas.map((area) {
final floorWorkAreaKey =
"${buildingKey}_${floor.floorName}_${area.areaName}";
final isExpanded =
floorExpansionState[floorWorkAreaKey] ?? false;
return ExpansionTile(
onExpansionChanged: (expanded) {
setMainState(() {
floorExpansionState[floorWorkAreaKey] = expanded;
});
},
trailing: Icon(
isExpanded
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
size: 28,
color: Colors.black54,
),
tilePadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 0),
title: Wrap(
spacing: 16,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
MyText.titleSmall(
"Floor: ${floor.floorName}",
fontWeight: 600,
color: Colors.teal,
maxLines: null,
overflow: TextOverflow.visible,
softWrap: true,
),
MyText.titleSmall(
"Work Area: ${area.areaName}",
fontWeight: 600,
color: Colors.blueGrey,
maxLines: null,
overflow: TextOverflow.visible,
softWrap: true,
),
],
),
childrenPadding: const EdgeInsets.only(
left: 16, right: 0, bottom: 8),
children: area.workItems.map((wItem) {
final item = wItem.workItem;
final completed = item.completedWork ?? 0;
final planned = item.plannedWork ?? 0;
final progress = (planned == 0)
? 0.0
: (completed / planned).clamp(0.0, 1.0);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: MyText.bodyMedium(
item.activityMaster?.name ??
"No Activity",
fontWeight: 600,
maxLines: 2,
overflow: TextOverflow.visible,
softWrap: true,
),
),
MySpacing.width(8),
if (item.workCategoryMaster?.name != null)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: Colors.blue.shade100,
borderRadius:
BorderRadius.circular(20),
),
child: MyText.bodySmall(
item.workCategoryMaster!.name!,
fontWeight: 500,
color: Colors.blue.shade800,
),
),
],
),
MySpacing.height(4),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 3,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
MySpacing.height(8),
MyText.bodySmall(
"Completed: $completed / $planned",
fontWeight: 600,
color: const Color.fromARGB(
221, 0, 0, 0),
),
],
),
),
MySpacing.width(16),
if (progress < 1.0)
IconButton(
icon: Icon(
Icons.person_add_alt_1_rounded,
color: const Color.fromARGB(
255, 46, 161, 233),
),
onPressed: () {
final pendingTask =
(planned - completed)
.clamp(0, planned);
showModalBottomSheet(
context: context,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.vertical(
top: Radius.circular(16)),
),
builder: (context) =>
AssignTaskBottomSheet(
buildingName: building.name,
floorName: floor.floorName,
workAreaName: area.areaName,
workLocation: area.areaName,
activityName:
item.activityMaster?.name ??
"Unknown Activity",
pendingTask: pendingTask,
workItemId: item.id.toString(),
assignmentDate: DateTime.now(),
),
);
},
),
],
),
MySpacing.height(8),
Stack(
children: [
Container(
height: 5,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(6),
),
),
FractionallySizedBox(
widthFactor: progress,
child: Container(
height: 5,
decoration: BoxDecoration(
color: progress >= 1.0
? Colors.green
: (progress >= 0.5
? Colors.amber
: Colors.red),
borderRadius:
BorderRadius.circular(6),
),
),
),
],
),
SizedBox(height: 4),
MyText.bodySmall(
"${(progress * 100).toStringAsFixed(1)}%",
fontWeight: 500,
color: progress >= 1.0
? Colors.green[700]
: (progress >= 0.5
? Colors.amber[800]
: Colors.red[700]),
),
],
),
);
}).toList(),
);
}).toList();
}).toList(),
),
),
);
}).toList(),
);
});
});
}
}