feat: Add custom skeleton loaders for employee list and daily progress report screens

This commit is contained in:
Vaibhav Surve 2025-06-19 16:42:24 +05:30
parent 97c873167f
commit 405916bb48
5 changed files with 207 additions and 81 deletions

View File

@ -0,0 +1,197 @@
import 'package:flutter/material.dart';
import 'package:marco/helpers/widgets/my_card.dart';
import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/utils/my_shadow.dart';
class SkeletonLoaders {
// Employee List - Card Style
static Widget employeeListSkeletonLoader() {
return Column(
children: List.generate(4, (index) {
return MyCard.bordered(
borderRadiusAll: 12,
paddingAll: 10,
margin: MySpacing.bottom(12),
shadow: MyShadow(elevation: 3),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Avatar
Container(
width: 41,
height: 41,
decoration: BoxDecoration(
color: Colors.grey.shade300,
shape: BoxShape.circle,
),
),
MySpacing.width(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(height: 14, width: 100, color: Colors.grey.shade300),
MySpacing.width(8),
Container(height: 12, width: 60, color: Colors.grey.shade300),
],
),
MySpacing.height(8),
Row(
children: [
Icon(Icons.email, size: 16, color: Colors.grey.shade300),
MySpacing.width(4),
Container(height: 10, width: 140, color: Colors.grey.shade300),
],
),
MySpacing.height(8),
Row(
children: [
Icon(Icons.phone, size: 16, color: Colors.grey.shade300),
MySpacing.width(4),
Container(height: 10, width: 100, color: Colors.grey.shade300),
],
),
],
),
),
],
),
);
}),
);
}
// Employee List - Compact Collapsed Style
static Widget employeeListCollapsedSkeletonLoader() {
return MyCard.bordered(
borderRadiusAll: 4,
paddingAll: 8,
child: Column(
children: List.generate(4, (index) {
return Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
// Avatar
Container(
width: 31,
height: 31,
decoration: BoxDecoration(
color: Colors.grey.shade300,
shape: BoxShape.circle,
),
),
MySpacing.width(16),
// Name, Designation & Buttons
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(height: 12, width: 100, color: Colors.grey.shade300),
MySpacing.height(8),
Container(height: 10, width: 80, color: Colors.grey.shade300),
MySpacing.height(12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(height: 28, width: 60, color: Colors.grey.shade300),
MySpacing.width(8),
Container(height: 28, width: 60, color: Colors.grey.shade300),
],
),
],
),
),
],
),
),
if (index != 3)
Divider(
color: Colors.grey.withOpacity(0.3),
thickness: 1,
height: 1,
),
],
);
}),
),
);
}
// Daily Progress Report Header Loader
static Widget dailyProgressReportSkeletonLoader() {
return MyCard.bordered(
borderRadiusAll: 4,
border: Border.all(color: Colors.grey.withOpacity(0.2)),
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
paddingAll: 8,
child: Column(
children: List.generate(3, (index) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(height: 14, width: 120, color: Colors.grey.shade300),
Icon(Icons.add_circle, color: Colors.grey.shade300),
],
),
if (index != 2) ...[
MySpacing.height(12),
Divider(color: Colors.grey.withOpacity(0.3), thickness: 1),
MySpacing.height(12),
],
],
);
}),
),
);
}
// Daily Progress Planning (Collapsed View)
static Widget dailyProgressPlanningSkeletonCollapsedOnly() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: List.generate(3, (index) {
return MyCard.bordered(
borderRadiusAll: 12,
paddingAll: 16,
margin: MySpacing.bottom(12),
shadow: MyShadow(elevation: 3),
child: Row(
children: [
// Icon placeholder
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.grey.shade300,
shape: BoxShape.circle,
),
),
MySpacing.width(12),
// Text line
Expanded(
child: Container(height: 16, color: Colors.grey.shade300),
),
MySpacing.width(12),
// Expand button placeholder
Container(
width: 28,
height: 28,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.shade300,
),
),
],
),
);
}),
);
}
}

View File

@ -18,6 +18,7 @@ import 'package:marco/model/attendance/attendence_action_button.dart';
import 'package:marco/model/attendance/regualrize_action_button.dart';
import 'package:marco/model/attendance/attendence_filter_sheet.dart';
import 'package:marco/controller/project_controller.dart';
import 'package:marco/helpers/widgets/my_custom_skeleton.dart';
class AttendanceScreen extends StatefulWidget {
AttendanceScreen({super.key});
@ -304,7 +305,7 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
),
),
if (isLoading)
employeeListSkeletonLoader()
SkeletonLoaders.employeeListSkeletonLoader()
else if (employees.isEmpty)
SizedBox(
height: 120,
@ -450,80 +451,6 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
});
}
Widget employeeListSkeletonLoader() {
return MyCard.bordered(
borderRadiusAll: 4,
paddingAll: 8,
child: Column(
children: List.generate(4, (index) {
return Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
// Avatar placeholder
Container(
width: 31,
height: 31,
decoration: BoxDecoration(
color: Colors.grey.shade300,
shape: BoxShape.circle,
),
),
MySpacing.width(16),
// Employee name/designation & buttons
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 12,
width: 100,
color: Colors.grey.shade300,
),
MySpacing.height(8),
Container(
height: 10,
width: 80,
color: Colors.grey.shade300,
),
MySpacing.height(12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
height: 28,
width: 60,
color: Colors.grey.shade300,
),
MySpacing.width(8),
Container(
height: 28,
width: 60,
color: Colors.grey.shade300,
),
],
),
],
),
),
],
),
),
if (index != 3)
Divider(
color: Colors.grey.withOpacity(0.3),
thickness: 1,
height: 1,
),
],
);
}),
),
);
}
Widget employeeLog() {
return Obx(() {
final logs = List.of(attendanceController.attendanceLogs);
@ -574,7 +501,7 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
),
),
if (attendanceController.isLoadingAttendanceLogs.value)
employeeListSkeletonLoader()
SkeletonLoaders.employeeListSkeletonLoader()
else if (logs.isEmpty)
SizedBox(
height: 120,
@ -759,7 +686,7 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
Obx(() {
final employees = attendanceController.regularizationLogs;
if (attendanceController.isLoadingRegularizationLogs.value) {
return employeeListSkeletonLoader();
return SkeletonLoaders.employeeListSkeletonLoader();
}
if (employees.isEmpty) {

View File

@ -14,7 +14,7 @@ import 'package:marco/controller/dashboard/employees_screen_controller.dart';
import 'package:marco/helpers/widgets/avatar.dart';
import 'package:marco/model/employees/employee_detail_bottom_sheet.dart';
import 'package:marco/controller/project_controller.dart';
import 'package:marco/helpers/widgets/my_custom_skeleton.dart';
class EmployeesScreen extends StatefulWidget {
const EmployeesScreen({super.key});
@ -292,7 +292,7 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
final isLoading = employeeScreenController.isLoading.value;
final employees = employeeScreenController.employees;
if (isLoading) {
return const Center(child: CircularProgressIndicator());
return SkeletonLoaders.employeeListSkeletonLoader();
}
if (employees.isEmpty) {
return Padding(

View File

@ -14,6 +14,7 @@ import 'package:marco/model/dailyTaskPlaning/daily_progress_report_filter.dart';
import 'package:marco/helpers/widgets/avatar.dart';
import 'package:marco/controller/project_controller.dart';
import 'package:marco/model/dailyTaskPlaning/task_action_buttons.dart';
import 'package:marco/helpers/widgets/my_custom_skeleton.dart';
class DailyProgressReportScreen extends StatefulWidget {
const DailyProgressReportScreen({super.key});
@ -297,7 +298,7 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
final groupedTasks = dailyTaskController.groupedDailyTasks;
if (isLoading) {
return const Center(child: CircularProgressIndicator());
return SkeletonLoaders.dailyProgressReportSkeletonLoader();
}
if (groupedTasks.isEmpty) {

View File

@ -11,6 +11,7 @@ import 'package:marco/controller/task_planing/daily_task_planing_controller.dart
import 'package:marco/controller/project_controller.dart';
import 'package:percent_indicator/percent_indicator.dart';
import 'package:marco/model/dailyTaskPlaning/assign_task_bottom_sheet .dart';
import 'package:marco/helpers/widgets/my_custom_skeleton.dart';
class DailyTaskPlaningScreen extends StatefulWidget {
DailyTaskPlaningScreen({super.key});
@ -170,7 +171,7 @@ class _DailyTaskPlaningScreenState extends State<DailyTaskPlaningScreen>
final dailyTasks = dailyTaskPlaningController.dailyTasks;
if (isLoading) {
return Center(child: CircularProgressIndicator());
return SkeletonLoaders.dailyProgressPlanningSkeletonCollapsedOnly();
}
if (dailyTasks.isEmpty) {