Refactor AttendanceScreen to improve project selection and refresh logic

This commit is contained in:
Vaibhav Surve 2025-05-16 16:24:11 +05:30
parent 2421233b9f
commit 501bec819f

View File

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