Refactor DailyTask and ReportTask screens for improved UI and functionality; update project selection and team member display logic.
This commit is contained in:
parent
8333910de4
commit
19e705f428
@ -50,7 +50,7 @@ class DailyTaskController extends GetxController {
|
||||
projects = response!.map((json) => ProjectModel.fromJson(json)).toList();
|
||||
selectedProjectId = projects.first.id.toString();
|
||||
log.i("Projects fetched: ${projects.length} projects loaded.");
|
||||
|
||||
update();
|
||||
await fetchTaskData(selectedProjectId);
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,13 @@ import 'package:get/get.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
final Logger logger = Logger();
|
||||
|
||||
enum ApiStatus { idle, loading, success, failure }
|
||||
class ReportTaskController extends MyController {
|
||||
List<PlatformFile> files = [];
|
||||
MyFormValidator basicValidator = MyFormValidator();
|
||||
RxBool isLoading = false.obs;
|
||||
Rx<ApiStatus> reportStatus = ApiStatus.idle.obs;
|
||||
Rx<ApiStatus> commentStatus = ApiStatus.idle.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -92,12 +94,17 @@ class ReportTaskController extends MyController {
|
||||
|
||||
final completedWork =
|
||||
basicValidator.getController('completed_work')?.text.trim();
|
||||
final commentField = basicValidator.getController('comment')?.text.trim();
|
||||
|
||||
if (completedWork == null || completedWork.isEmpty) {
|
||||
Get.snackbar("Error", "Completed work is required.");
|
||||
return;
|
||||
}
|
||||
final completedWorkInt = int.tryParse(completedWork);
|
||||
if (completedWorkInt == null || completedWorkInt <= 0) {
|
||||
Get.snackbar("Error", "Completed work must be a positive integer.");
|
||||
return;
|
||||
}
|
||||
final commentField = basicValidator.getController('comment')?.text.trim();
|
||||
|
||||
if (commentField == null || commentField.isEmpty) {
|
||||
Get.snackbar("Error", "Comment is required.");
|
||||
@ -126,6 +133,7 @@ class ReportTaskController extends MyController {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> commentTask({
|
||||
required String projectId,
|
||||
required String comment,
|
||||
@ -158,13 +166,13 @@ class ReportTaskController extends MyController {
|
||||
);
|
||||
|
||||
if (success) {
|
||||
Get.snackbar("Success", "Task reported successfully!");
|
||||
Get.snackbar("Success", "Task commented successfully!");
|
||||
} else {
|
||||
Get.snackbar("Error", "Failed to report task.");
|
||||
Get.snackbar("Error", "Failed to comment task.");
|
||||
}
|
||||
} catch (e) {
|
||||
logger.e("Error reporting task: $e");
|
||||
Get.snackbar("Error", "An error occurred while reporting the task.");
|
||||
logger.e("Error commenting task: $e");
|
||||
Get.snackbar("Error", "An error occurred while commenting the task.");
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import 'package:logger/logger.dart';
|
||||
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||
import 'package:marco/helpers/services/auth_service.dart';
|
||||
import 'package:marco/helpers/services/api_endpoints.dart';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
final Logger logger = Logger();
|
||||
|
||||
class ApiService {
|
||||
@ -332,6 +332,7 @@ class ApiService {
|
||||
final json = jsonDecode(response.body);
|
||||
|
||||
if (response.statusCode == 200 && json['success'] == true) {
|
||||
Get.back();
|
||||
return true;
|
||||
} else {
|
||||
_log("Failed to report task: ${json['message'] ?? 'Unknown error'}");
|
||||
@ -358,9 +359,10 @@ class ApiService {
|
||||
final json = jsonDecode(response.body);
|
||||
|
||||
if (response.statusCode == 200 && json['success'] == true) {
|
||||
Get.back();
|
||||
return true;
|
||||
} else {
|
||||
_log("Failed to report task: ${json['message'] ?? 'Unknown error'}");
|
||||
_log("Failed to comment task: ${json['message'] ?? 'Unknown error'}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
54
lib/helpers/widgets/my_team_model_sheet.dart
Normal file
54
lib/helpers/widgets/my_team_model_sheet.dart
Normal file
@ -0,0 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:marco/helpers/widgets/avatar.dart';
|
||||
import 'package:marco/helpers/widgets/my_text.dart';
|
||||
|
||||
class TeamBottomSheet {
|
||||
static void show({
|
||||
required BuildContext context,
|
||||
required List<dynamic> teamMembers,
|
||||
}) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
builder: (_) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title and Close Icon
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
MyText.bodyLarge("Team Members", fontWeight: 600),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close, size: 20, color: Colors.black54),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(thickness: 1.2),
|
||||
// Team Member Rows
|
||||
...teamMembers.map((member) => _buildTeamMemberRow(member)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildTeamMemberRow(dynamic member) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Avatar(firstName: member.firstName, lastName: '', size: 36),
|
||||
const SizedBox(width: 10),
|
||||
MyText.bodyMedium(member.firstName, fontWeight: 500),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ import 'package:marco/model/my_paginated_table.dart';
|
||||
import 'package:marco/controller/dashboard/daily_task_controller.dart';
|
||||
import 'package:marco/helpers/widgets/avatar.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:marco/helpers/widgets/my_team_model_sheet.dart';
|
||||
class DailyTaskScreen extends StatefulWidget {
|
||||
const DailyTaskScreen({super.key});
|
||||
|
||||
@ -63,7 +63,7 @@ class _DailyTaskScreenState extends State<DailyTaskScreen> with UIMixin {
|
||||
return Padding(
|
||||
padding: MySpacing.x(flexSpacing),
|
||||
child: MyText.titleMedium(
|
||||
"Daily Task",
|
||||
"Daily Progress Report",
|
||||
fontSize: 18,
|
||||
fontWeight: 600,
|
||||
),
|
||||
@ -76,7 +76,7 @@ class _DailyTaskScreenState extends State<DailyTaskScreen> with UIMixin {
|
||||
child: MyBreadcrumb(
|
||||
children: [
|
||||
MyBreadcrumbItem(name: 'Dashboard'),
|
||||
MyBreadcrumbItem(name: 'Daily Task', active: true),
|
||||
MyBreadcrumbItem(name: 'Daily Progress Report', active: true),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -120,9 +120,16 @@ class _DailyTaskScreenState extends State<DailyTaskScreen> with UIMixin {
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
offset: const Offset(0, 40),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
dailyTaskController.selectedProjectId == null
|
||||
? dailyTaskController.projects.isNotEmpty
|
||||
@ -130,10 +137,15 @@ class _DailyTaskScreenState extends State<DailyTaskScreen> with UIMixin {
|
||||
: 'No Tasks'
|
||||
: dailyTaskController.projects
|
||||
.firstWhere((project) =>
|
||||
project.id == dailyTaskController.selectedProjectId)
|
||||
project.id ==
|
||||
dailyTaskController.selectedProjectId)
|
||||
.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w600),
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
const Icon(Icons.arrow_drop_down),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -141,14 +153,28 @@ class _DailyTaskScreenState extends State<DailyTaskScreen> with UIMixin {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _buildDateRangeButton() {
|
||||
String dateRangeText;
|
||||
if (dailyTaskController.startDateTask != null &&
|
||||
dailyTaskController.endDateTask != null) {
|
||||
dateRangeText =
|
||||
'${DateFormat('dd-MM-yyyy').format(dailyTaskController.startDateTask!)}'
|
||||
' to '
|
||||
'${DateFormat('dd-MM-yyyy').format(dailyTaskController.endDateTask!)}';
|
||||
} else {
|
||||
dateRangeText = "Select Date Range";
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.date_range),
|
||||
label: const Text("Select Date Range"),
|
||||
label: Text(dateRangeText),
|
||||
onPressed: () => dailyTaskController.selectDateRangeForTaskData(
|
||||
context, dailyTaskController),
|
||||
context,
|
||||
dailyTaskController,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -334,7 +360,9 @@ class _DailyTaskScreenState extends State<DailyTaskScreen> with UIMixin {
|
||||
|
||||
final teamMembers =
|
||||
task.teamMembers.map((member) => member.firstName).toList();
|
||||
final taskComments = task.comments.map((comment) => comment.comment ?? 'No Content').toList();
|
||||
final taskComments = task.comments
|
||||
.map((comment) => comment.comment ?? 'No Content')
|
||||
.toList();
|
||||
Get.toNamed(
|
||||
'/daily-task/comment-task',
|
||||
arguments: {
|
||||
@ -366,66 +394,44 @@ class _DailyTaskScreenState extends State<DailyTaskScreen> with UIMixin {
|
||||
|
||||
Widget _buildTeamCell(dynamic task) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
onTap: () => TeamBottomSheet.show(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
title: MyText.bodyMedium("Team Members"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: task.teamMembers.map<Widget>((member) {
|
||||
return ListTile(
|
||||
leading: Avatar(
|
||||
firstName: member.firstName,
|
||||
lastName: '',
|
||||
size: 32,
|
||||
teamMembers: task.teamMembers,
|
||||
),
|
||||
title: Text(member.firstName),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: MyText.bodyMedium("Close"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 32,
|
||||
width: 100,
|
||||
child: Stack(
|
||||
children: [
|
||||
for (int i = 0; i < task.teamMembers.length.clamp(0, 3); i++)
|
||||
Positioned(
|
||||
left: i * 24.0,
|
||||
child: Tooltip(
|
||||
message: task.teamMembers[i].firstName,
|
||||
child: Avatar(
|
||||
firstName: task.teamMembers[i].firstName,
|
||||
lastName: '',
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildAvatar(task.teamMembers[i], i * 24.0),
|
||||
if (task.teamMembers.length > 3)
|
||||
Positioned(
|
||||
left: 2 * 24.0,
|
||||
child: CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Colors.grey.shade300,
|
||||
child: MyText.bodyMedium(
|
||||
'+${task.teamMembers.length - 3}',
|
||||
style: const TextStyle(fontSize: 12, color: Colors.black87),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildExtraMembersIndicator(task.teamMembers.length - 3, 48.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAvatar(dynamic member, double leftPosition) {
|
||||
return Positioned(
|
||||
left: leftPosition,
|
||||
child: Tooltip(
|
||||
message: member.firstName,
|
||||
child: Avatar(firstName: member.firstName, lastName: '', size: 32),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildExtraMembersIndicator(int extraMembers, double leftPosition) {
|
||||
return Positioned(
|
||||
left: leftPosition,
|
||||
child: CircleAvatar(
|
||||
radius: 16,
|
||||
backgroundColor: Colors.grey.shade300,
|
||||
child: MyText.bodyMedium('+$extraMembers',
|
||||
style: const TextStyle(fontSize: 12, color: Colors.black87)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -51,12 +51,18 @@ class DashboardScreen extends StatelessWidget with UIMixin {
|
||||
|
||||
List<Widget> _buildDashboardStats() {
|
||||
final stats = [
|
||||
_StatItem(LucideIcons.gauge, "Dashboard", contentTheme.primary, dashboardRoute),
|
||||
_StatItem(LucideIcons.scan_face, "Attendance", contentTheme.success, attendanceRoute),
|
||||
_StatItem( LucideIcons.users, "Employees", contentTheme.warning, employeesRoute),
|
||||
_StatItem(LucideIcons.logs, "Daily Task", contentTheme.info, tasksRoute),
|
||||
_StatItem(LucideIcons.logs, "Daily Task Planing", contentTheme.info, tasksRoute),
|
||||
_StatItem(LucideIcons.folder, "Projects", contentTheme.secondary, projectsRoute),
|
||||
_StatItem(
|
||||
LucideIcons.gauge, "Dashboard", contentTheme.primary, dashboardRoute),
|
||||
_StatItem(LucideIcons.scan_face, "Attendance", contentTheme.success,
|
||||
attendanceRoute),
|
||||
_StatItem(
|
||||
LucideIcons.users, "Employees", contentTheme.warning, employeesRoute),
|
||||
_StatItem(LucideIcons.logs, "Daily Progress Report", contentTheme.info,
|
||||
tasksRoute),
|
||||
_StatItem(LucideIcons.logs, "Daily Task Planing", contentTheme.info,
|
||||
tasksRoute),
|
||||
_StatItem(LucideIcons.folder, "Projects", contentTheme.secondary,
|
||||
projectsRoute),
|
||||
];
|
||||
|
||||
return List.generate(
|
||||
|
@ -126,7 +126,7 @@ class _LeftBarState extends State<LeftBar>
|
||||
route: '/dashboard/employees'),
|
||||
NavigationItem(
|
||||
iconData: LucideIcons.list,
|
||||
title: "Daily Task",
|
||||
title: "Daily Progress Report",
|
||||
isCondensed: isCondensed,
|
||||
route: '/dashboard/daily-task'),
|
||||
],
|
||||
|
@ -16,6 +16,7 @@ import 'package:marco/helpers/widgets/my_text.dart';
|
||||
import 'package:marco/helpers/widgets/my_text_style.dart';
|
||||
import 'package:marco/view/layouts/layout.dart';
|
||||
import 'package:marco/helpers/widgets/avatar.dart';
|
||||
import 'package:marco/helpers/widgets/my_team_model_sheet.dart';
|
||||
|
||||
class CommentTaskScreen extends StatefulWidget {
|
||||
const CommentTaskScreen({super.key});
|
||||
@ -23,6 +24,10 @@ class CommentTaskScreen extends StatefulWidget {
|
||||
@override
|
||||
State<CommentTaskScreen> createState() => _CommentTaskScreenState();
|
||||
}
|
||||
class _Member {
|
||||
final String firstName;
|
||||
_Member(this.firstName);
|
||||
}
|
||||
|
||||
class _CommentTaskScreenState extends State<CommentTaskScreen> with UIMixin {
|
||||
final ReportTaskController controller = Get.put(ReportTaskController());
|
||||
@ -67,7 +72,7 @@ class _CommentTaskScreenState extends State<CommentTaskScreen> with UIMixin {
|
||||
fontSize: 18, fontWeight: 600),
|
||||
MyBreadcrumb(
|
||||
children: [
|
||||
MyBreadcrumbItem(name: 'Daily Task'),
|
||||
MyBreadcrumbItem(name: 'Daily Progress Report'),
|
||||
MyBreadcrumbItem(name: 'Comment Task'),
|
||||
],
|
||||
),
|
||||
@ -234,7 +239,12 @@ Widget buildTeamMembers() {
|
||||
MyText.labelMedium("Team Members:"),
|
||||
MySpacing.width(12),
|
||||
GestureDetector(
|
||||
onTap: () => showTeamMembersDialog(members),
|
||||
onTap: () {
|
||||
TeamBottomSheet.show(
|
||||
context: context,
|
||||
teamMembers: members.map((name) => _Member(name)).toList(),
|
||||
);
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 32,
|
||||
width: 100,
|
||||
@ -274,37 +284,6 @@ Widget buildTeamMembers() {
|
||||
);
|
||||
}
|
||||
|
||||
void showTeamMembersDialog(List<String> members) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: MyText.titleMedium('Team Members'),
|
||||
content: SizedBox(
|
||||
width: 300,
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: members.length,
|
||||
itemBuilder: (context, index) {
|
||||
final name = members[index];
|
||||
return ListTile(
|
||||
leading: Avatar(firstName: name, lastName: "", size: 32),
|
||||
title: MyText.bodyMedium(name),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: MyText.bodySmall("Close"),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildRow(String label, String? value) {
|
||||
print("Label: $label, Value: $value");
|
||||
return Padding(
|
||||
|
@ -62,7 +62,7 @@ class _ReportTaskScreenState extends State<ReportTaskScreen> with UIMixin {
|
||||
fontSize: 18, fontWeight: 600),
|
||||
MyBreadcrumb(
|
||||
children: [
|
||||
MyBreadcrumbItem(name: 'Daily Task'),
|
||||
MyBreadcrumbItem(name: 'Daily Progress Report'),
|
||||
MyBreadcrumbItem(name: 'Report Task'),
|
||||
],
|
||||
),
|
||||
@ -195,18 +195,21 @@ class _ReportTaskScreenState extends State<ReportTaskScreen> with UIMixin {
|
||||
),
|
||||
MySpacing.width(12),
|
||||
MyButton(
|
||||
onPressed: () async {
|
||||
onPressed: controller.reportStatus.value == ApiStatus.loading
|
||||
? null
|
||||
: () async {
|
||||
if (controller.basicValidator.validateForm()) {
|
||||
await controller.reportTask(
|
||||
projectId: controller.basicValidator
|
||||
.getController('task_id')
|
||||
?.text ??
|
||||
'', // Replace with actual ID
|
||||
'',
|
||||
comment: controller.basicValidator
|
||||
.getController('comment')
|
||||
?.text ??
|
||||
'',
|
||||
completedTask: int.tryParse(controller.basicValidator
|
||||
completedTask: int.tryParse(controller
|
||||
.basicValidator
|
||||
.getController('completed_work')
|
||||
?.text ??
|
||||
'') ??
|
||||
@ -220,20 +223,27 @@ class _ReportTaskScreenState extends State<ReportTaskScreen> with UIMixin {
|
||||
padding: MySpacing.xy(20, 16),
|
||||
backgroundColor: contentTheme.primary,
|
||||
borderRadiusAll: AppStyle.buttonRadius.medium,
|
||||
child: MyText.bodySmall(
|
||||
child: Obx(() {
|
||||
if (controller.reportStatus.value == ApiStatus.loading) {
|
||||
return SizedBox(
|
||||
height: 16,
|
||||
width: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
contentTheme.onPrimary),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return MyText.bodySmall(
|
||||
'Save',
|
||||
color: contentTheme.onPrimary,
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Loading spinner
|
||||
Obx(() {
|
||||
return controller.isLoading.value
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: SizedBox.shrink();
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
Loading…
x
Reference in New Issue
Block a user