Add Daily Task feature with controller, model, and UI integration
- Implement DailyTaskController for managing daily tasks and fetching projects. - Create TaskModel to represent task data structure. - Develop DailyTaskScreen for displaying tasks with filtering options. - Update routes to include Daily Task navigation. - Enhance DashboardScreen to link to Daily Task. - Add Daily Task option in the left navigation bar.
This commit is contained in:
parent
809c048de6
commit
db0b525e87
118
lib/controller/dashboard/daily_task_controller.dart
Normal file
118
lib/controller/dashboard/daily_task_controller.dart
Normal file
@ -0,0 +1,118 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:marco/helpers/services/api_service.dart';
|
||||
import 'package:marco/model/project_model.dart';
|
||||
import 'package:marco/model/daily_task_model.dart';
|
||||
|
||||
final Logger log = Logger();
|
||||
|
||||
class DailyTaskController extends GetxController {
|
||||
List<ProjectModel> projects = [];
|
||||
String? selectedProjectId;
|
||||
|
||||
DateTime? startDateTask;
|
||||
DateTime? endDateTask;
|
||||
|
||||
List<TaskModel> dailyTasks = [];
|
||||
|
||||
RxBool isLoading = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_initializeDefaults();
|
||||
}
|
||||
|
||||
void _initializeDefaults() {
|
||||
_setDefaultDateRange();
|
||||
fetchProjects();
|
||||
}
|
||||
|
||||
void _setDefaultDateRange() {
|
||||
final today = DateTime.now();
|
||||
startDateTask = today.subtract(const Duration(days: 7));
|
||||
endDateTask = today;
|
||||
log.i("Default date range set: $startDateTask to $endDateTask");
|
||||
}
|
||||
|
||||
Future<void> fetchProjects() async {
|
||||
isLoading.value = true;
|
||||
|
||||
final response = await ApiService.getProjects();
|
||||
isLoading.value = false;
|
||||
|
||||
if (response?.isEmpty ?? true) {
|
||||
log.w("No project data found or API call failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
projects = response!.map((json) => ProjectModel.fromJson(json)).toList();
|
||||
selectedProjectId = projects.first.id.toString();
|
||||
log.i("Projects fetched: ${projects.length} projects loaded.");
|
||||
|
||||
await fetchTaskData(selectedProjectId);
|
||||
}
|
||||
|
||||
Future<void> fetchTaskData(String? projectId) async {
|
||||
if (projectId == null) return;
|
||||
|
||||
isLoading.value = true;
|
||||
final response = await ApiService.getDailyTasks(
|
||||
projectId,
|
||||
dateFrom: startDateTask,
|
||||
dateTo: endDateTask,
|
||||
);
|
||||
isLoading.value = false;
|
||||
|
||||
if (response != null) {
|
||||
Map<String, List<TaskModel>> groupedTasks = {};
|
||||
|
||||
for (var taskJson in response) {
|
||||
TaskModel task = TaskModel.fromJson(taskJson);
|
||||
String assignmentDateKey = task.assignmentDate;
|
||||
|
||||
if (groupedTasks.containsKey(assignmentDateKey)) {
|
||||
groupedTasks[assignmentDateKey]?.add(task);
|
||||
} else {
|
||||
groupedTasks[assignmentDateKey] = [task];
|
||||
}
|
||||
}
|
||||
dailyTasks = groupedTasks.entries
|
||||
.map((entry) => entry.value)
|
||||
.expand((taskList) => taskList)
|
||||
.toList();
|
||||
|
||||
log.i("Daily tasks fetched and grouped: ${dailyTasks.length}");
|
||||
|
||||
update();
|
||||
} else {
|
||||
log.e("Failed to fetch daily tasks for project $projectId");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> selectDateRangeForTaskData(
|
||||
BuildContext context,
|
||||
DailyTaskController controller,
|
||||
) async {
|
||||
final picked = await showDateRangePicker(
|
||||
context: context,
|
||||
firstDate: DateTime(2022),
|
||||
lastDate: DateTime.now(),
|
||||
initialDateRange: DateTimeRange(
|
||||
start: startDateTask ?? DateTime.now().subtract(const Duration(days: 7)),
|
||||
end: endDateTask ?? DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
if (picked == null) return;
|
||||
|
||||
startDateTask = picked.start;
|
||||
endDateTask = picked.end;
|
||||
|
||||
log.i("Date range selected: $startDateTask to $endDateTask");
|
||||
|
||||
await controller.fetchTaskData(controller.selectedProjectId);
|
||||
}
|
||||
}
|
@ -1,40 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:marco/helpers/widgets/my_container.dart';
|
||||
import 'package:marco/helpers/widgets/my_text.dart';
|
||||
|
||||
class Avatar extends StatelessWidget {
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final double size;
|
||||
final Color backgroundColor;
|
||||
final Color? backgroundColor; // Optional: allows override
|
||||
final Color textColor;
|
||||
|
||||
// Constructor
|
||||
|
||||
const Avatar({
|
||||
super.key,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
this.size = 46.0, // Default size
|
||||
this.backgroundColor = Colors.blue, // Default background color
|
||||
this.textColor = Colors.white, // Default text color
|
||||
this.size = 46.0,
|
||||
this.backgroundColor,
|
||||
this.textColor = Colors.white,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Extract first letters of firstName and lastName
|
||||
String initials = "${firstName.isNotEmpty ? firstName[0] : ''}${lastName.isNotEmpty ? lastName[0] : ''}".toUpperCase();
|
||||
|
||||
final Color bgColor = backgroundColor ?? _generateColorFromName('$firstName$lastName');
|
||||
|
||||
return MyContainer.rounded(
|
||||
height: size,
|
||||
width: size,
|
||||
paddingAll: 0,
|
||||
color: backgroundColor, // Background color of the avatar
|
||||
color: bgColor,
|
||||
child: Center(
|
||||
child: MyText.labelSmall(
|
||||
initials,
|
||||
fontWeight: 600,
|
||||
color: textColor, // Text color of the initials
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Generate a consistent "random-like" color from the name
|
||||
Color _generateColorFromName(String name) {
|
||||
final hash = name.hashCode;
|
||||
final r = (hash & 0xFF0000) >> 16;
|
||||
final g = (hash & 0x00FF00) >> 8;
|
||||
final b = (hash & 0x0000FF);
|
||||
return Color.fromARGB(255, r, g, b).withOpacity(1.0);
|
||||
}
|
||||
}
|
||||
|
153
lib/model/daily_task_model.dart
Normal file
153
lib/model/daily_task_model.dart
Normal file
@ -0,0 +1,153 @@
|
||||
class TaskModel {
|
||||
final String assignmentDate;
|
||||
final WorkItem? workItem;
|
||||
final int plannedTask;
|
||||
final int completedTask;
|
||||
final AssignedBy assignedBy;
|
||||
final List<TeamMember> teamMembers;
|
||||
final List<Comment> comments;
|
||||
|
||||
// Remove plannedWork and completedWork from direct properties
|
||||
int get plannedWork => workItem?.plannedWork ?? 0;
|
||||
int get completedWork => workItem?.completedWork ?? 0;
|
||||
|
||||
TaskModel({
|
||||
required this.assignmentDate,
|
||||
required this.workItem,
|
||||
required this.plannedTask,
|
||||
required this.completedTask,
|
||||
required this.assignedBy,
|
||||
required this.teamMembers,
|
||||
required this.comments,
|
||||
});
|
||||
|
||||
factory TaskModel.fromJson(Map<String, dynamic> json) {
|
||||
final workItemJson = json['workItem'];
|
||||
final workItem = workItemJson != null ? WorkItem.fromJson(workItemJson) : null;
|
||||
|
||||
return TaskModel(
|
||||
assignmentDate: json['assignmentDate'],
|
||||
workItem: workItem,
|
||||
plannedTask: json['plannedTask'],
|
||||
completedTask: json['completedTask'],
|
||||
assignedBy: AssignedBy.fromJson(json['assignedBy']),
|
||||
teamMembers: (json['teamMembers'] as List)
|
||||
.map((e) => TeamMember.fromJson(e))
|
||||
.toList(),
|
||||
comments: (json['comments'] as List)
|
||||
.map((e) => Comment.fromJson(e))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkItem {
|
||||
final ActivityMaster? activityMaster;
|
||||
final WorkArea? workArea;
|
||||
|
||||
// Add plannedWork and completedWork as properties of WorkItem
|
||||
final int? plannedWork;
|
||||
final int? completedWork;
|
||||
|
||||
WorkItem({
|
||||
this.activityMaster,
|
||||
this.workArea,
|
||||
this.plannedWork,
|
||||
this.completedWork,
|
||||
});
|
||||
|
||||
factory WorkItem.fromJson(Map<String, dynamic> json) {
|
||||
return WorkItem(
|
||||
activityMaster: json['activityMaster'] != null
|
||||
? ActivityMaster.fromJson(json['activityMaster'])
|
||||
: null,
|
||||
workArea: json['workArea'] != null ? WorkArea.fromJson(json['workArea']) : null,
|
||||
plannedWork: json['plannedWork'],
|
||||
completedWork: json['completedWork'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ActivityMaster {
|
||||
final String activityName;
|
||||
|
||||
ActivityMaster({required this.activityName});
|
||||
|
||||
factory ActivityMaster.fromJson(Map<String, dynamic> json) {
|
||||
return ActivityMaster(activityName: json['activityName']);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkArea {
|
||||
final String areaName;
|
||||
final Floor? floor;
|
||||
|
||||
WorkArea({required this.areaName, this.floor});
|
||||
|
||||
factory WorkArea.fromJson(Map<String, dynamic> json) {
|
||||
return WorkArea(
|
||||
areaName: json['areaName'],
|
||||
floor: json['floor'] != null ? Floor.fromJson(json['floor']) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Floor {
|
||||
final String floorName;
|
||||
final Building? building;
|
||||
|
||||
Floor({required this.floorName, this.building});
|
||||
|
||||
factory Floor.fromJson(Map<String, dynamic> json) {
|
||||
return Floor(
|
||||
floorName: json['floorName'],
|
||||
building:
|
||||
json['building'] != null ? Building.fromJson(json['building']) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Building {
|
||||
final String name;
|
||||
|
||||
Building({required this.name});
|
||||
|
||||
factory Building.fromJson(Map<String, dynamic> json) {
|
||||
return Building(name: json['name']);
|
||||
}
|
||||
}
|
||||
|
||||
class AssignedBy {
|
||||
final String firstName;
|
||||
final String? lastName;
|
||||
|
||||
AssignedBy({required this.firstName, this.lastName});
|
||||
|
||||
factory AssignedBy.fromJson(Map<String, dynamic> json) {
|
||||
return AssignedBy(
|
||||
firstName: json['firstName'],
|
||||
lastName: json['lastName'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TeamMember {
|
||||
final String firstName;
|
||||
|
||||
TeamMember({required this.firstName});
|
||||
|
||||
factory TeamMember.fromJson(Map<String, dynamic> json) {
|
||||
return TeamMember(firstName: json['firstName']);
|
||||
}
|
||||
}
|
||||
|
||||
class Comment {
|
||||
final String comment;
|
||||
|
||||
Comment({required this.comment});
|
||||
|
||||
factory Comment.fromJson(Map<String, dynamic> json) {
|
||||
return Comment(comment: json['comment']);
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ import 'package:marco/view/dashboard/attendanceScreen.dart';
|
||||
import 'package:marco/view/dashboard/dashboard_screen.dart';
|
||||
import 'package:marco/view/dashboard/add_employee_screen.dart';
|
||||
import 'package:marco/view/dashboard/employee_screen.dart';
|
||||
import 'package:marco/view/dashboard/daily_task_screen.dart';
|
||||
|
||||
class AuthMiddleware extends GetMiddleware {
|
||||
@override
|
||||
@ -47,6 +48,11 @@ getPageRoute() {
|
||||
name: '/employees/addEmployee',
|
||||
page: () => AddEmployeeScreen(),
|
||||
middlewares: [AuthMiddleware()]),
|
||||
// Daily Task Planning
|
||||
GetPage(
|
||||
name: '/dashboard/daily-task',
|
||||
page: () => DailyTaskScreen(),
|
||||
middlewares: [AuthMiddleware()]),
|
||||
// Authentication
|
||||
GetPage(name: '/auth/login', page: () => LoginScreen()),
|
||||
GetPage(
|
||||
|
356
lib/view/dashboard/daily_task_screen.dart
Normal file
356
lib/view/dashboard/daily_task_screen.dart
Normal file
@ -0,0 +1,356 @@
|
||||
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/widgets/my_breadcrumb.dart';
|
||||
import 'package:marco/helpers/widgets/my_breadcrumb_item.dart';
|
||||
import 'package:marco/helpers/widgets/my_flex.dart';
|
||||
import 'package:marco/helpers/widgets/my_flex_item.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/helpers/widgets/my_loading_component.dart';
|
||||
import 'package:marco/helpers/widgets/my_refresh_wrapper.dart';
|
||||
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';
|
||||
|
||||
class DailyTaskScreen extends StatefulWidget {
|
||||
const DailyTaskScreen({super.key});
|
||||
|
||||
@override
|
||||
State<DailyTaskScreen> createState() => _DailyTaskScreenState();
|
||||
}
|
||||
|
||||
class _DailyTaskScreenState extends State<DailyTaskScreen> with UIMixin {
|
||||
final DailyTaskController dailyTaskController =
|
||||
Get.put(DailyTaskController());
|
||||
final PermissionController permissionController =
|
||||
Get.put(PermissionController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Layout(
|
||||
child: Obx(() {
|
||||
return LoadingComponent(
|
||||
isLoading: dailyTaskController.isLoading.value,
|
||||
loadingText: 'Loading Tasks...',
|
||||
child: GetBuilder<DailyTaskController>(
|
||||
init: dailyTaskController,
|
||||
builder: (controller) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildHeader(),
|
||||
MySpacing.height(flexSpacing),
|
||||
_buildBreadcrumb(),
|
||||
MySpacing.height(flexSpacing),
|
||||
_buildFilterSection(),
|
||||
MySpacing.height(flexSpacing),
|
||||
_buildTaskList(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Padding(
|
||||
padding: MySpacing.x(flexSpacing),
|
||||
child: MyText.titleMedium(
|
||||
"Daily Task",
|
||||
fontSize: 18,
|
||||
fontWeight: 600,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBreadcrumb() {
|
||||
return Padding(
|
||||
padding: MySpacing.x(flexSpacing),
|
||||
child: MyBreadcrumb(
|
||||
children: [
|
||||
MyBreadcrumbItem(name: 'Dashboard'),
|
||||
MyBreadcrumbItem(name: 'Daily Task', active: true),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterSection() {
|
||||
return Padding(
|
||||
padding: MySpacing.x(flexSpacing),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildProjectFilter(),
|
||||
const SizedBox(width: 10),
|
||||
_buildDateRangeButton(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProjectFilter() {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.black, width: 1.5),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: PopupMenuButton<String>(
|
||||
onSelected: (String value) async {
|
||||
if (value.isNotEmpty) {
|
||||
dailyTaskController.selectedProjectId = value;
|
||||
await dailyTaskController.fetchTaskData(value);
|
||||
}
|
||||
dailyTaskController.update();
|
||||
},
|
||||
itemBuilder: (BuildContext context) {
|
||||
return dailyTaskController.projects
|
||||
.map<PopupMenuItem<String>>((project) {
|
||||
return PopupMenuItem<String>(
|
||||
value: project.id,
|
||||
child: MyText.bodySmall(project.name),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||
child: Text(
|
||||
dailyTaskController.selectedProjectId == null
|
||||
? dailyTaskController.projects.isNotEmpty
|
||||
? dailyTaskController.projects.first.name
|
||||
: 'No Tasks'
|
||||
: dailyTaskController.projects
|
||||
.firstWhere((project) =>
|
||||
project.id == dailyTaskController.selectedProjectId)
|
||||
.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDateRangeButton() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.date_range),
|
||||
label: const Text("Select Date Range"),
|
||||
onPressed: () => dailyTaskController.selectDateRangeForTaskData(
|
||||
context, dailyTaskController),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTaskList() {
|
||||
return Padding(
|
||||
padding: MySpacing.x(flexSpacing / 2),
|
||||
child: MyFlex(
|
||||
children: [
|
||||
MyFlexItem(sizes: 'lg-6', child: employeeListTab()),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget employeeListTab() {
|
||||
if (dailyTaskController.dailyTasks.isEmpty) {
|
||||
return Center(
|
||||
child: MyText.bodySmall("No Tasks Assigned to This Project",
|
||||
fontWeight: 600),
|
||||
);
|
||||
}
|
||||
Map<String, List<dynamic>> groupedTasks = {};
|
||||
for (var task in dailyTaskController.dailyTasks) {
|
||||
String dateKey =
|
||||
DateFormat('dd-MM-yyyy').format(DateTime.parse(task.assignmentDate));
|
||||
groupedTasks.putIfAbsent(dateKey, () => []).add(task);
|
||||
}
|
||||
|
||||
// Sort dates descending (latest first)
|
||||
final sortedEntries = groupedTasks.entries.toList()
|
||||
..sort((a, b) => DateFormat('dd-MM-yyyy')
|
||||
.parse(b.key)
|
||||
.compareTo(DateFormat('dd-MM-yyyy').parse(a.key)));
|
||||
|
||||
// Flatten grouped data into one list with optional visual separators
|
||||
List<DataRow> allRows = [];
|
||||
|
||||
for (var entry in sortedEntries) {
|
||||
allRows.add(
|
||||
DataRow(
|
||||
color: WidgetStateProperty.all(Colors.grey.shade200),
|
||||
cells: [
|
||||
DataCell(MyText.titleSmall('Date: ${entry.key}')),
|
||||
DataCell(MyText.titleSmall('')),
|
||||
DataCell(MyText.titleSmall('')),
|
||||
DataCell(MyText.titleSmall('')),
|
||||
DataCell(MyText.titleSmall('')),
|
||||
DataCell(MyText.titleSmall('')),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
allRows.addAll(entry.value.map((task) => _buildRow(task)));
|
||||
}
|
||||
|
||||
return MyRefreshableContent(
|
||||
onRefresh: () async {
|
||||
if (dailyTaskController.selectedProjectId != null) {
|
||||
await dailyTaskController
|
||||
.fetchTaskData(dailyTaskController.selectedProjectId!);
|
||||
}
|
||||
},
|
||||
child: MyPaginatedTable(
|
||||
columns: _buildColumns(),
|
||||
rows: allRows,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<DataColumn> _buildColumns() {
|
||||
return [
|
||||
DataColumn(
|
||||
label: MyText.labelLarge('Activity', color: contentTheme.primary)),
|
||||
DataColumn(
|
||||
label: MyText.labelLarge('Assigned', color: contentTheme.primary)),
|
||||
DataColumn(
|
||||
label: MyText.labelLarge('Completed', color: contentTheme.primary)),
|
||||
DataColumn(
|
||||
label: MyText.labelLarge('Assigned On', color: contentTheme.primary)),
|
||||
DataColumn(label: MyText.labelLarge('Team', color: contentTheme.primary)),
|
||||
DataColumn(
|
||||
label: MyText.labelLarge('Actions', color: contentTheme.primary)),
|
||||
];
|
||||
}
|
||||
|
||||
DataRow _buildRow(dynamic task) {
|
||||
final workItem = task.workItem;
|
||||
final location = [
|
||||
workItem?.workArea?.floor?.building?.name,
|
||||
workItem?.workArea?.floor?.floorName,
|
||||
workItem?.workArea?.areaName
|
||||
].where((e) => e != null && e.isNotEmpty).join(' > ');
|
||||
|
||||
return DataRow(cells: [
|
||||
DataCell(Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
MyText.bodyMedium(workItem?.activityMaster?.activityName ?? 'N/A'),
|
||||
const SizedBox(height: 2),
|
||||
MyText.bodySmall(location),
|
||||
],
|
||||
)),
|
||||
DataCell(
|
||||
MyText.bodyMedium(
|
||||
'${task.plannedTask ?? "NA"} / '
|
||||
'${(workItem?.plannedWork != null && workItem?.completedWork != null) ? (workItem!.plannedWork! - workItem.completedWork!) : "NA"}',
|
||||
),
|
||||
),
|
||||
DataCell(MyText.bodyMedium(task.completedTask.toString())),
|
||||
DataCell(MyText.bodyMedium(DateFormat('dd-MM-yyyy')
|
||||
.format(DateTime.parse(task.assignmentDate)))),
|
||||
DataCell(_buildTeamCell(task)),
|
||||
DataCell(Row(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
|
||||
minimumSize: const Size(60, 20),
|
||||
textStyle: const TextStyle(fontSize: 12),
|
||||
),
|
||||
child: const Text("Report"),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () {},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
|
||||
minimumSize: const Size(60, 20),
|
||||
textStyle: const TextStyle(fontSize: 12),
|
||||
),
|
||||
child: const Text("Comment"),
|
||||
),
|
||||
],
|
||||
)),
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _buildTeamCell(dynamic task) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -15,7 +15,11 @@ import 'package:marco/view/layouts/layout.dart';
|
||||
class DashboardScreen extends StatelessWidget with UIMixin {
|
||||
DashboardScreen({super.key});
|
||||
|
||||
static const String dashboardRoute = "/dashboard/attendance";
|
||||
static const String dashboardRoute = "/dashboard";
|
||||
static const String employeesRoute = "/dashboard/employees";
|
||||
static const String projectsRoute = "/dashboard";
|
||||
static const String attendanceRoute = "/dashboard/attendance";
|
||||
static const String tasksRoute = "/dashboard/daily-task";
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -47,10 +51,12 @@ class DashboardScreen extends StatelessWidget with UIMixin {
|
||||
|
||||
List<Widget> _buildDashboardStats() {
|
||||
final stats = [
|
||||
_StatItem(LucideIcons.gauge, "Dashboard", contentTheme.primary),
|
||||
_StatItem(LucideIcons.folder, "Projects", contentTheme.secondary),
|
||||
_StatItem(LucideIcons.scan_face, "Attendance", contentTheme.success),
|
||||
_StatItem(LucideIcons.logs, "Task", contentTheme.info),
|
||||
_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),
|
||||
];
|
||||
|
||||
return List.generate(
|
||||
@ -59,7 +65,8 @@ class DashboardScreen extends StatelessWidget with UIMixin {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildStatCard(stats[index * 2]),
|
||||
if (index * 2 + 1 < stats.length) _buildStatCard(stats[index * 2 + 1]),
|
||||
if (index * 2 + 1 < stats.length)
|
||||
_buildStatCard(stats[index * 2 + 1]),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -68,7 +75,7 @@ class DashboardScreen extends StatelessWidget with UIMixin {
|
||||
Widget _buildStatCard(_StatItem statItem) {
|
||||
return Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => Get.toNamed(dashboardRoute),
|
||||
onTap: () => Get.toNamed(statItem.route),
|
||||
child: MyCard.bordered(
|
||||
borderRadiusAll: 10,
|
||||
border: Border.all(color: Colors.grey.withOpacity(0.2)),
|
||||
@ -105,6 +112,7 @@ class _StatItem {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final Color color;
|
||||
final String route; // New field to store the route for each stat item
|
||||
|
||||
_StatItem(this.icon, this.title, this.color);
|
||||
_StatItem(this.icon, this.title, this.color, this.route);
|
||||
}
|
||||
|
@ -124,6 +124,11 @@ class _LeftBarState extends State<LeftBar>
|
||||
title: "Employees",
|
||||
isCondensed: isCondensed,
|
||||
route: '/dashboard/employees'),
|
||||
NavigationItem(
|
||||
iconData: LucideIcons.list,
|
||||
title: "Daily Task",
|
||||
isCondensed: isCondensed,
|
||||
route: '/dashboard/daily-task'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
Loading…
x
Reference in New Issue
Block a user