Refactor AttendanceScreen to improve project selection and refresh logic #23
@ -40,200 +40,181 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Layout(
|
||||
child: MyRefreshableContent(
|
||||
onRefresh: () async {
|
||||
if (attendanceController.selectedProjectId != null) {
|
||||
await attendanceController.fetchEmployeesByProject(
|
||||
attendanceController.selectedProjectId!);
|
||||
await attendanceController
|
||||
.fetchAttendanceLogs(attendanceController.selectedProjectId!);
|
||||
await attendanceController
|
||||
.fetchProjectData(attendanceController.selectedProjectId!);
|
||||
await attendanceController
|
||||
.fetchProjectData(attendanceController.selectedProjectId!);
|
||||
attendanceController.update();
|
||||
} else {
|
||||
await attendanceController.fetchProjects();
|
||||
}
|
||||
},
|
||||
child: GetBuilder<AttendanceController>(
|
||||
init: attendanceController,
|
||||
tag: 'attendance_dashboard_controller',
|
||||
builder: (controller) {
|
||||
return LoadingComponent(
|
||||
isLoading: controller.isLoading.value,
|
||||
loadingText: 'Loading Attendance...',
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: MySpacing.x(flexSpacing),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
MyText.titleMedium("Attendance",
|
||||
fontSize: 18, fontWeight: 600),
|
||||
MyBreadcrumb(
|
||||
children: [
|
||||
MyBreadcrumbItem(name: 'Dashboard'),
|
||||
MyBreadcrumbItem(name: 'Attendance', active: true),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
child: GetBuilder<AttendanceController>(
|
||||
init: attendanceController,
|
||||
tag: 'attendance_dashboard_controller',
|
||||
builder: (controller) {
|
||||
return LoadingComponent(
|
||||
isLoading: controller.isLoading.value,
|
||||
loadingText: 'Loading Attendance...',
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: MySpacing.x(flexSpacing),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
MyText.titleMedium("Attendance",
|
||||
fontSize: 18, fontWeight: 600),
|
||||
MyBreadcrumb(
|
||||
children: [
|
||||
MyBreadcrumbItem(name: 'Dashboard'),
|
||||
MyBreadcrumbItem(name: 'Attendance', active: true),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
MySpacing.height(flexSpacing),
|
||||
Padding(
|
||||
padding: MySpacing.x(flexSpacing / 2),
|
||||
child: MyFlex(
|
||||
children: [
|
||||
// Project Selection Dropdown
|
||||
MyFlexItem(
|
||||
sizes: 'lg-12',
|
||||
child: MyContainer.bordered(
|
||||
padding: MySpacing.xy(8, 8),
|
||||
child: PopupMenuButton<String>(
|
||||
onSelected: (value) async {
|
||||
attendanceController.selectedProjectId = value;
|
||||
await attendanceController
|
||||
.fetchEmployeesByProject(value);
|
||||
await attendanceController
|
||||
.fetchAttendanceLogs(value);
|
||||
await attendanceController
|
||||
.fetchRegularizationLogs(value);
|
||||
await attendanceController
|
||||
.fetchProjectData(value);
|
||||
attendanceController.update();
|
||||
},
|
||||
itemBuilder: (BuildContext context) {
|
||||
if (attendanceController.projects.isEmpty) {
|
||||
return [
|
||||
PopupMenuItem<String>(
|
||||
value: '',
|
||||
child: MyText.bodySmall('No Data',
|
||||
fontWeight: 600),
|
||||
)
|
||||
];
|
||||
}
|
||||
// Filter projects based on permissions
|
||||
final accessibleProjects = attendanceController
|
||||
.projects
|
||||
.where((project) => permissionController
|
||||
.isUserAssignedToProject(
|
||||
project.id.toString()))
|
||||
.toList();
|
||||
),
|
||||
MySpacing.height(flexSpacing),
|
||||
Padding(
|
||||
padding: MySpacing.x(flexSpacing / 2),
|
||||
child: MyFlex(
|
||||
children: [
|
||||
// Project Selection Dropdown
|
||||
MyFlexItem(
|
||||
sizes: 'lg-12',
|
||||
child: MyContainer.bordered(
|
||||
padding: MySpacing.xy(8, 8),
|
||||
child: PopupMenuButton<String>(
|
||||
onSelected: (value) async {
|
||||
attendanceController.selectedProjectId = value;
|
||||
await attendanceController
|
||||
.fetchEmployeesByProject(value);
|
||||
await attendanceController
|
||||
.fetchAttendanceLogs(value);
|
||||
await attendanceController
|
||||
.fetchRegularizationLogs(value);
|
||||
await attendanceController
|
||||
.fetchProjectData(value);
|
||||
attendanceController.update();
|
||||
},
|
||||
itemBuilder: (BuildContext context) {
|
||||
if (attendanceController.projects.isEmpty) {
|
||||
return [
|
||||
PopupMenuItem<String>(
|
||||
value: '',
|
||||
child: MyText.bodySmall('No Data',
|
||||
fontWeight: 600),
|
||||
)
|
||||
];
|
||||
}
|
||||
// Filter projects based on permissions
|
||||
final accessibleProjects = attendanceController
|
||||
.projects
|
||||
.where((project) => permissionController
|
||||
.isUserAssignedToProject(
|
||||
project.id.toString()))
|
||||
.toList();
|
||||
|
||||
if (accessibleProjects.isEmpty) {
|
||||
return [
|
||||
PopupMenuItem<String>(
|
||||
value: '',
|
||||
child: MyText.bodySmall(
|
||||
'No Projects Assigned',
|
||||
fontWeight: 600),
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
return accessibleProjects.map((project) {
|
||||
return PopupMenuItem<String>(
|
||||
value: project.id.toString(),
|
||||
height: 32,
|
||||
if (accessibleProjects.isEmpty) {
|
||||
return [
|
||||
PopupMenuItem<String>(
|
||||
value: '',
|
||||
child: MyText.bodySmall(
|
||||
project.name,
|
||||
color: theme.colorScheme.onSurface,
|
||||
fontWeight: 600,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
color: theme.cardTheme.color,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
MyText.labelSmall(
|
||||
attendanceController.selectedProjectId !=
|
||||
null
|
||||
? attendanceController.projects
|
||||
.firstWhereOrNull((proj) =>
|
||||
proj.id ==
|
||||
attendanceController
|
||||
.selectedProjectId)
|
||||
?.name ??
|
||||
'Select a Project'
|
||||
: 'Select a Project',
|
||||
'No Projects Assigned',
|
||||
fontWeight: 600),
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
return accessibleProjects.map((project) {
|
||||
return PopupMenuItem<String>(
|
||||
value: project.id.toString(),
|
||||
height: 32,
|
||||
child: MyText.bodySmall(
|
||||
project.name,
|
||||
color: theme.colorScheme.onSurface,
|
||||
fontWeight: 600,
|
||||
),
|
||||
Icon(LucideIcons.chevron_down,
|
||||
size: 20,
|
||||
color: theme.colorScheme.onSurface),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
color: theme.cardTheme.color,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
MyText.labelSmall(
|
||||
attendanceController.selectedProjectId != null
|
||||
? attendanceController.projects
|
||||
.firstWhereOrNull((proj) =>
|
||||
proj.id ==
|
||||
attendanceController
|
||||
.selectedProjectId)
|
||||
?.name ??
|
||||
'Select a Project'
|
||||
: 'Select a Project',
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
Icon(LucideIcons.chevron_down,
|
||||
size: 20,
|
||||
color: theme.colorScheme.onSurface),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Tab Section
|
||||
MyFlexItem(
|
||||
sizes: 'lg-12',
|
||||
child: Obx(() {
|
||||
bool hasRegularizationPermission =
|
||||
permissionController.hasPermission(
|
||||
Permissions.regularizeAttendance);
|
||||
// Tab Section
|
||||
MyFlexItem(
|
||||
sizes: 'lg-12',
|
||||
child: Obx(() {
|
||||
bool hasRegularizationPermission =
|
||||
permissionController.hasPermission(
|
||||
Permissions.regularizeAttendance);
|
||||
|
||||
final tabs = <Tab>[
|
||||
const Tab(text: 'Employee List'),
|
||||
const Tab(text: 'Logs'),
|
||||
if (hasRegularizationPermission)
|
||||
const Tab(text: 'Regularization'),
|
||||
];
|
||||
final tabs = <Tab>[
|
||||
const Tab(text: 'Employee List'),
|
||||
const Tab(text: 'Logs'),
|
||||
if (hasRegularizationPermission)
|
||||
const Tab(text: 'Regularization'),
|
||||
];
|
||||
|
||||
final views = <Widget>[
|
||||
employeeListTab(),
|
||||
reportsTab(context),
|
||||
if (hasRegularizationPermission)
|
||||
regularizationTab(context),
|
||||
];
|
||||
final views = <Widget>[
|
||||
employeeListTab(),
|
||||
reportsTab(context),
|
||||
if (hasRegularizationPermission)
|
||||
regularizationTab(context),
|
||||
];
|
||||
|
||||
return DefaultTabController(
|
||||
length: tabs.length,
|
||||
child: MyCard.bordered(
|
||||
borderRadiusAll: 4,
|
||||
border: Border.all(
|
||||
color: Colors.grey.withAlpha(50)),
|
||||
shadow: MyShadow(
|
||||
elevation: 1,
|
||||
position: MyShadowPosition.bottom),
|
||||
paddingAll: 10,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TabBar(
|
||||
labelColor: theme.colorScheme.primary,
|
||||
unselectedLabelColor: theme
|
||||
.colorScheme.onSurface
|
||||
.withAlpha(150),
|
||||
tabs: tabs,
|
||||
),
|
||||
MySpacing.height(16),
|
||||
SizedBox(
|
||||
height: 550,
|
||||
child: TabBarView(children: views),
|
||||
),
|
||||
],
|
||||
),
|
||||
return DefaultTabController(
|
||||
length: tabs.length,
|
||||
child: MyCard.bordered(
|
||||
borderRadiusAll: 4,
|
||||
border:
|
||||
Border.all(color: Colors.grey.withAlpha(50)),
|
||||
shadow: MyShadow(
|
||||
elevation: 1,
|
||||
position: MyShadowPosition.bottom),
|
||||
paddingAll: 10,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TabBar(
|
||||
labelColor: theme.colorScheme.primary,
|
||||
unselectedLabelColor: theme
|
||||
.colorScheme.onSurface
|
||||
.withAlpha(150),
|
||||
tabs: tabs,
|
||||
),
|
||||
MySpacing.height(16),
|
||||
SizedBox(
|
||||
height: 550,
|
||||
child: TabBarView(children: views),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -365,13 +346,25 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
||||
),
|
||||
]);
|
||||
}).toList();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(0.0),
|
||||
child: SingleChildScrollView(
|
||||
child: MyPaginatedTable(
|
||||
columns: columns,
|
||||
rows: rows,
|
||||
child: MyRefreshableContent(
|
||||
onRefresh: () async {
|
||||
if (attendanceController.selectedProjectId != null) {
|
||||
await attendanceController.fetchEmployeesByProject(
|
||||
attendanceController.selectedProjectId!);
|
||||
await attendanceController
|
||||
.fetchProjectData(attendanceController.selectedProjectId!);
|
||||
attendanceController.update();
|
||||
} else {
|
||||
await attendanceController.fetchProjects();
|
||||
}
|
||||
},
|
||||
child: MyPaginatedTable(
|
||||
columns: columns,
|
||||
rows: rows,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -832,11 +825,28 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.date_range),
|
||||
label: const Text("Select Date Range for Attendance"),
|
||||
onPressed: () => attendanceController
|
||||
.selectDateRangeForAttendance(context, attendanceController),
|
||||
child: GetBuilder<AttendanceController>(
|
||||
id: 'attendance_dashboard_controller',
|
||||
builder: (controller) {
|
||||
String labelText;
|
||||
if (controller.startDateAttendance != null &&
|
||||
controller.endDateAttendance != null) {
|
||||
final start = DateFormat('dd MM yyyy')
|
||||
.format(controller.startDateAttendance!);
|
||||
final end = DateFormat('dd MM yyyy')
|
||||
.format(controller.endDateAttendance!);
|
||||
labelText = "$start - $end";
|
||||
} else {
|
||||
labelText = "Select Date Range for Attendance";
|
||||
}
|
||||
|
||||
return TextButton.icon(
|
||||
icon: const Icon(Icons.date_range),
|
||||
label: Text(labelText, overflow: TextOverflow.ellipsis),
|
||||
onPressed: () => controller.selectDateRangeForAttendance(
|
||||
Get.context!, controller),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (attendanceController.attendanceLogs.isEmpty)
|
||||
@ -851,12 +861,25 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
||||
),
|
||||
)
|
||||
else
|
||||
SingleChildScrollView(
|
||||
child: MyPaginatedTable(
|
||||
columns: columns,
|
||||
rows: rows,
|
||||
MyRefreshableContent(
|
||||
onRefresh: () async {
|
||||
if (attendanceController.selectedProjectId != null) {
|
||||
await attendanceController.fetchAttendanceLogs(
|
||||
attendanceController.selectedProjectId!);
|
||||
await attendanceController.fetchProjectData(
|
||||
attendanceController.selectedProjectId!);
|
||||
attendanceController.update();
|
||||
} else {
|
||||
await attendanceController.fetchProjects();
|
||||
}
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
child: MyPaginatedTable(
|
||||
columns: columns,
|
||||
rows: rows,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -1120,14 +1143,27 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: MyPaginatedTable(
|
||||
columns: columns,
|
||||
rows: rows,
|
||||
columnSpacing: 15.0,
|
||||
child: MyRefreshableContent(
|
||||
onRefresh: () async {
|
||||
if (attendanceController.selectedProjectId != null) {
|
||||
await attendanceController.fetchProjectData(
|
||||
attendanceController.selectedProjectId!);
|
||||
await attendanceController.fetchRegularizationLogs(
|
||||
attendanceController.selectedProjectId!);
|
||||
attendanceController.update();
|
||||
} else {
|
||||
await attendanceController.fetchProjects();
|
||||
}
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
child: MyPaginatedTable(
|
||||
columns: columns,
|
||||
rows: rows,
|
||||
columnSpacing: 15.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user