import 'package:flutter/material.dart'; import 'package:on_field_work/helpers/widgets/my_card.dart'; import 'package:on_field_work/helpers/widgets/my_spacing.dart'; import 'package:on_field_work/helpers/utils/my_shadow.dart'; class SkeletonLoaders { static Widget buildLoadingSkeleton() { return ShimmerEffect( child: SizedBox( height: 360, child: Column( children: List.generate(5, (index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: List.generate(6, (i) { return Container( margin: const EdgeInsets.symmetric(horizontal: 4), width: 48, height: 16, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(6), ), ); }), ), ), ); }), ), ), ); } static Widget attendanceQuickCardSkeleton() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), gradient: LinearGradient( colors: [ Colors.grey.shade300.withOpacity(0.3), Colors.grey.shade300.withOpacity(0.6), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), child: ShimmerEffect( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Row with avatar and texts Row( children: [ // Avatar Container( width: 30, height: 30, decoration: BoxDecoration( color: Colors.grey.shade400, shape: BoxShape.circle, ), ), MySpacing.width(10), // Name + designation Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 12, width: 100, color: Colors.grey.shade400, ), MySpacing.height(6), Container( height: 10, width: 70, color: Colors.grey.shade400, ), ], ), ), // Status Container( height: 12, width: 60, color: Colors.grey.shade400, ), ], ), const SizedBox(height: 12), // Description Container( height: 10, width: double.infinity, color: Colors.grey.shade400, ), MySpacing.height(6), Container( height: 10, width: double.infinity, color: Colors.grey.shade400, ), const SizedBox(height: 12), // Action buttons Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Container( height: 28, width: 80, decoration: BoxDecoration( color: Colors.grey.shade400, borderRadius: BorderRadius.circular(4), ), ), MySpacing.width(8), Container( height: 28, width: 28, decoration: BoxDecoration( color: Colors.grey.shade400, shape: BoxShape.circle, ), ), ], ), ], ), ), ); } static Widget dashboardCardsSkeleton({double? maxWidth}) { return LayoutBuilder(builder: (context, constraints) { double width = maxWidth ?? constraints.maxWidth; int crossAxisCount = (width ~/ 80).clamp(2, 4); double cardWidth = (width - (crossAxisCount - 1) * 6) / crossAxisCount; return Wrap( spacing: 6, runSpacing: 6, children: List.generate(6, (index) { return MyCard.bordered( width: cardWidth, height: 60, paddingAll: 4, borderRadiusAll: 5, border: Border.all(color: Colors.grey.withOpacity(0.15)), child: ShimmerEffect( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 16, height: 16, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), MySpacing.height(4), Container( width: cardWidth * 0.5, height: 10, color: Colors.grey.shade300, ), ], ), ), ); }), ); }); } static Widget paymentRequestListSkeletonLoader() { return ListView.separated( padding: const EdgeInsets.fromLTRB(12, 12, 12, 80), itemCount: 6, separatorBuilder: (_, __) => Divider(color: Colors.grey.shade300, height: 20), itemBuilder: (context, index) { return ShimmerEffect( child: Container( padding: const EdgeInsets.symmetric(vertical: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Category name placeholder Container( height: 14, width: 120, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 6), // Payee placeholder Row( children: [ Container( height: 12, width: 50, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(width: 8), Expanded( child: Container( height: 12, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), ), ], ), const SizedBox(height: 6), // Due date and status placeholders Row( children: [ // Due date label + value Row( children: [ Container( height: 12, width: 50, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(width: 6), Container( height: 12, width: 80, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), ], ), const Spacer(), // Status chip placeholder Container( height: 20, width: 60, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(6), ), ), ], ), ], ), ), ); }, ); } static Widget paymentRequestDetailSkeletonLoader() { return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(12, 12, 12, 30), child: Center( child: Container( constraints: const BoxConstraints(maxWidth: 520), child: MyCard.bordered( paddingAll: 16, borderRadiusAll: 8, shadow: MyShadow(elevation: 3), child: ShimmerEffect( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header (Created At + Status) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( width: 140, height: 16, color: Colors.grey.shade300, ), Container( width: 80, height: 20, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(5), ), ), ], ), MySpacing.height(24), // Parties Section ...List.generate( 4, (index) => Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Container( height: 14, width: double.infinity, color: Colors.grey.shade300, ), )), MySpacing.height(24), // Details Table ...List.generate( 6, (index) => Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Container( height: 14, width: double.infinity, color: Colors.grey.shade300, ), )), MySpacing.height(24), // Documents Section Column( crossAxisAlignment: CrossAxisAlignment.start, children: List.generate( 3, (index) => Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Container( height: 40, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(8), ), ), )), ), MySpacing.height(24), // Logs / Timeline Column( children: List.generate( 3, (index) => Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 16, height: 16, decoration: BoxDecoration( color: Colors.grey.shade300, shape: BoxShape.circle, ), ), MySpacing.width(12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 14, width: 120, color: Colors.grey.shade300, ), MySpacing.height(6), Container( height: 12, width: double.infinity, color: Colors.grey.shade300, ), MySpacing.height(6), Container( height: 12, width: 80, color: Colors.grey.shade300, ), MySpacing.height(16), ], ), ), ], )), ), ], ), ), ), ), ), ); } static Widget employeeDetailSkeletonLoader() { return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(12, 20, 12, 80), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Header skeleton (avatar + name + role) MyCard( borderRadiusAll: 8, paddingAll: 16, margin: MySpacing.bottom(16), shadow: MyShadow(elevation: 2), child: ShimmerEffect( child: Row( children: [ // Avatar Container( width: 45, height: 45, decoration: BoxDecoration( color: Colors.grey.shade300, shape: BoxShape.circle, ), ), MySpacing.width(16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 14, width: 120, color: Colors.grey.shade300, ), MySpacing.height(6), Container( height: 12, width: 80, color: Colors.grey.shade300, ), ], ), ), Container( width: 24, height: 24, color: Colors.grey.shade300, ), ], ), ), ), // Sections skeleton ...List.generate( 4, (_) => Column( children: [ MyCard( borderRadiusAll: 8, paddingAll: 16, margin: MySpacing.bottom(16), shadow: MyShadow(elevation: 2), child: ShimmerEffect( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Section header Row( children: [ Container( width: 20, height: 20, color: Colors.grey.shade300, ), MySpacing.width(8), Container( width: 120, height: 14, color: Colors.grey.shade300, ), ], ), MySpacing.height(8), const Divider(color: Colors.grey), // 2 rows placeholder ...List.generate( 2, (_) => Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 20, height: 20, color: Colors.grey.shade300, ), MySpacing.width(16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 12, width: 100, color: Colors.grey.shade300, ), MySpacing.height(4), Container( height: 12, width: 150, color: Colors.grey.shade300, ), ], ), ), Container( width: 20, height: 20, color: Colors.grey.shade300, ), ], ), )), ], ), ), ), ], ), ), ], ), ); } static Widget dailyProgressPlanningInfraSkeleton() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: List.generate(3, (floorIndex) { return MyCard( borderRadiusAll: 8, paddingAll: 5, margin: MySpacing.bottom(10), shadow: MyShadow(elevation: 1.5), child: ShimmerEffect( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Floor header placeholder Container( height: 14, width: 160, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(6), ), ), MySpacing.height(10), // Divider Divider(color: Colors.grey.withOpacity(0.3)), // Work areas skeleton Column( children: List.generate(2, (areaIndex) { return Padding( padding: const EdgeInsets.only(top: 8, bottom: 6), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Area title Container( height: 12, width: 120, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), MySpacing.height(8), // Work items skeleton rows Column( children: List.generate(2, (itemIndex) { return Padding( padding: const EdgeInsets.only(top: 4), child: Row( children: [ // Bullet / icon Container( width: 8, height: 8, decoration: BoxDecoration( color: Colors.grey.shade300, shape: BoxShape.circle, ), ), MySpacing.width(8), // Item text placeholder Expanded( child: Container( height: 10, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), ), ], ), ); }), ), ], ), ); }), ), ], ), ), ); }), ); } static Widget chartSkeletonLoader() { return MyCard.bordered( paddingAll: 16, borderRadiusAll: 12, shadow: MyShadow( elevation: 1.5, position: MyShadowPosition.bottom, ), child: ShimmerEffect( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Chart Header Placeholder Container( height: 16, width: 180, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(6), ), ), const SizedBox(height: 16), // Donut Skeleton Placeholder Center( child: Container( width: 180, height: 180, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.grey.shade300.withOpacity(0.5), ), ), ), const SizedBox(height: 16), // Legend placeholders Wrap( spacing: 8, runSpacing: 8, children: List.generate(5, (index) { return Container( width: 100, height: 14, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(6), ), ); }), ), ], ), ), ); } static Widget dateSkeletonLoader() { return ShimmerEffect( child: Container( height: 14, width: 90, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(6), ), ), ); } static Widget expenseByStatusSkeletonLoader() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.05), blurRadius: 6, spreadRadius: 1, offset: const Offset(0, 2), ), ], ), child: ShimmerEffect( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Title Container( height: 16, width: 160, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(6), ), ), const SizedBox(height: 16), // 4 Status Rows ...List.generate(4, (index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( children: [ // Icon placeholder Container( height: 44, width: 44, decoration: BoxDecoration( color: Colors.grey.shade300, shape: BoxShape.circle, ), ), const SizedBox(width: 12), // Title + Amount Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 12, width: 100, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 6), Container( height: 12, width: 60, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), ], ), ), // Count + arrow placeholder Container( height: 12, width: 30, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(width: 6), Icon(Icons.chevron_right, color: Colors.grey.shade300, size: 24), ], ), ); }), const SizedBox(height: 16), Divider(color: Colors.grey.shade300), const SizedBox(height: 12), // Bottom Row (Project Spendings) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 12, width: 120, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 4), Container( height: 10, width: 140, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), ], ), Container( height: 16, width: 80, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), ], ), ], ), ), ); } static Widget documentSkeletonLoader() { return Column( children: List.generate(5, (index) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Date placeholder Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), child: ShimmerEffect( child: Container( height: 12, width: 80, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(6), ), ), ), ), // Document Card Skeleton Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: ShimmerEffect( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Icon Placeholder Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(8), ), child: const Icon(Icons.description, color: Colors.transparent), // invisible icon ), const SizedBox(width: 12), // Text placeholders Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 12, width: 80, color: Colors.grey.shade300, ), MySpacing.height(6), Container( height: 14, width: double.infinity, color: Colors.grey.shade300, ), MySpacing.height(6), Container( height: 12, width: 100, color: Colors.grey.shade300, ), ], ), ), // Action icon placeholder Container( width: 20, height: 20, decoration: BoxDecoration( color: Colors.grey.shade300, shape: BoxShape.circle, ), ), ], ), ), ), ], ); }), ); } static Widget documentDetailsSkeletonLoader() { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Details Card Container( constraints: const BoxConstraints(maxWidth: 460), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.06), blurRadius: 16, offset: const Offset(0, 4), ), ], ), child: ShimmerEffect( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header Row( children: [ Container( width: 56, height: 56, decoration: BoxDecoration( color: Colors.grey.shade300, shape: BoxShape.circle, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 16, width: 180, color: Colors.grey.shade300, ), const SizedBox(height: 8), Container( height: 12, width: 120, color: Colors.grey.shade300, ), ], ), ), ], ), const SizedBox(height: 12), // Tags placeholder Wrap( spacing: 6, runSpacing: 6, children: List.generate(3, (index) { return Container( height: 20, width: 60, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(12), ), ); }), ), const SizedBox(height: 16), // Info rows placeholders Column( children: List.generate(10, (index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( children: [ Container( height: 12, width: 120, color: Colors.grey.shade300, ), const SizedBox(width: 12), Expanded( child: Container( height: 12, color: Colors.grey.shade300, ), ), ], ), ); }), ), ], ), ), ), const SizedBox(height: 20), // Versions section skeleton Container( margin: const EdgeInsets.symmetric(horizontal: 12), child: ShimmerEffect( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: List.generate(3, (index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(8), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 12, width: 180, color: Colors.grey.shade300, ), const SizedBox(height: 6), Container( height: 10, width: 120, color: Colors.grey.shade300, ), ], ), ), Container( width: 24, height: 24, decoration: BoxDecoration( color: Colors.grey.shade300, shape: BoxShape.circle, ), ), ], ), ); }), ), ), ), ], ), ); } 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: ShimmerEffect( 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), ], ), ], ), ), ], ), ), ); }), ); } static Widget employeeListCollapsedSkeletonLoader() { return MyCard.bordered( borderRadiusAll: 4, paddingAll: 8, child: ShimmerEffect( 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, ), ], ); }), ), ), ); } 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: ShimmerEffect( 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), ], ], ); }), ), ), ); } 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: ShimmerEffect( 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, ), ), ], ), ), ); }), ); } static Widget expenseListSkeletonLoader() { return ListView.separated( padding: const EdgeInsets.fromLTRB(12, 12, 12, 80), itemCount: 6, separatorBuilder: (_, __) => Divider(color: Colors.grey.shade300, height: 20), itemBuilder: (context, index) { return ShimmerEffect( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Title and Amount Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( height: 14, width: 120, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(6), ), ), Container( height: 14, width: 80, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(6), ), ), ], ), const SizedBox(height: 6), // Date and Status Row( children: [ Container( height: 12, width: 100, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(6), ), ), const Spacer(), Container( height: 12, width: 50, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(6), ), ), ], ), ], ), ); }, ); } static Widget employeeSkeletonCard() { return MyCard.bordered( margin: MySpacing.only(bottom: 12), paddingAll: 12, borderRadiusAll: 12, shadow: MyShadow( elevation: 1.5, position: MyShadowPosition.bottom, ), child: ShimmerEffect( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Avatar Container( height: 35, width: 35, decoration: BoxDecoration( color: Colors.grey.shade300, shape: BoxShape.circle, ), ), MySpacing.width(12), // Name, org, email, phone Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 12, width: 120, color: Colors.grey.shade300), MySpacing.height(6), Container(height: 10, width: 80, color: Colors.grey.shade300), MySpacing.height(8), // Email placeholder Row( children: [ Icon(Icons.email_outlined, size: 14, color: Colors.grey.shade300), MySpacing.width(4), Container( height: 10, width: 140, color: Colors.grey.shade300), ], ), MySpacing.height(8), // Phone placeholder Row( children: [ Icon(Icons.phone_outlined, size: 14, color: Colors.grey.shade300), MySpacing.width(4), Container( height: 10, width: 100, color: Colors.grey.shade300), MySpacing.width(8), Container( height: 16, width: 16, decoration: BoxDecoration( color: Colors.grey.shade300, shape: BoxShape.circle, ), ), ], ), MySpacing.height(8), // Tags placeholder Container(height: 8, width: 80, color: Colors.grey.shade300), ], ), ), // Arrow Icon(Icons.arrow_forward_ios, size: 14, color: Colors.grey.shade300), ], ), ), ); } static Widget contactSkeletonCard() { return MyCard.bordered( margin: MySpacing.only(bottom: 12), paddingAll: 16, borderRadiusAll: 16, shadow: MyShadow( elevation: 1.5, position: MyShadowPosition.bottom, ), child: ShimmerEffect( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( height: 40, width: 40, decoration: BoxDecoration( color: Colors.grey.shade300, shape: BoxShape.circle, ), ), MySpacing.width(12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 12, width: 100, color: Colors.grey.shade300, ), MySpacing.height(6), Container( height: 10, width: 60, color: Colors.grey.shade300, ), ], ), ), ], ), MySpacing.height(16), Container(height: 10, width: 150, color: Colors.grey.shade300), MySpacing.height(8), Container(height: 10, width: 100, color: Colors.grey.shade300), MySpacing.height(8), Container(height: 10, width: 120, color: Colors.grey.shade300), ], ), ), ); } // ==================================================================== // NEW SKELETON LOADER METHODS // ==================================================================== /// Skeleton for the CollectionsHealthWidget static Widget collectionHealthSkeleton() { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 5), ), ], ), padding: const EdgeInsets.all(16.0), child: ShimmerEffect( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header Skeleton Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 14, width: 180, color: Colors.grey.shade300), MySpacing.height(4), Container( height: 10, width: 140, color: Colors.grey.shade300), ], ), ), ], ), const SizedBox(height: 20), // Main Content Row (Left Chart + Right Metrics) Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Left Chart Section Expanded( flex: 5, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Gauge Chart Placeholder Container( width: 120, height: 80, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.vertical( top: Radius.circular(60), bottom: Radius.zero), ), alignment: Alignment.bottomCenter, child: Container( height: 12, width: 60, color: Colors.grey.shade400), ), const SizedBox(height: 20), // Summary Text Placeholders Container( height: 16, width: 150, color: Colors.grey.shade300), MySpacing.height(6), Container( height: 12, width: 200, color: Colors.grey.shade300), MySpacing.height(4), Container( height: 12, width: 100, color: Colors.grey.shade300), ], ), ), const SizedBox(width: 16), // Right Metrics Section Expanded( flex: 4, child: Column( children: [ // Metric Card 1 _buildMetricCardSkeleton(), const SizedBox(height: 10), // Metric Card 2 _buildMetricCardSkeleton(), ], ), ), ], ), const SizedBox(height: 20), // Aging Analysis Section Container(height: 14, width: 220, color: Colors.grey.shade300), MySpacing.height(4), Container(height: 10, width: 180, color: Colors.grey.shade300), const SizedBox(height: 10), // Aging Stacked Bar Placeholder ClipRRect( borderRadius: BorderRadius.circular(8), child: Row( children: List.generate( 4, (index) => Expanded( flex: 1, child: Container( height: 16, color: Colors.grey.shade400), )), ), ), const SizedBox(height: 15), // Aging Legend Placeholders Wrap( spacing: 12, runSpacing: 8, children: List.generate( 4, (index) => Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 10, height: 10, decoration: BoxDecoration( color: Colors.grey.shade300, shape: BoxShape.circle)), const SizedBox(width: 6), Container( height: 12, width: 120, color: Colors.grey.shade300), ], )), ), ], ), ), ); } /// Helper for Metric Card Skeleton static Widget _buildMetricCardSkeleton() { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), decoration: BoxDecoration( color: Colors.grey.shade200, // Background color for the card borderRadius: BorderRadius.circular(5), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container(height: 10, width: 90, color: Colors.grey.shade300), MySpacing.height(4), Container(height: 12, width: 100, color: Colors.grey.shade300), MySpacing.height(4), Container(height: 14, width: 80, color: Colors.grey.shade300), ], ), ); } /// Skeleton for the CompactPurchaseInvoiceDashboard static Widget purchaseInvoiceDashboardSkeleton() { return Container( padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: 15, offset: const Offset(0, 5), ), ], ), child: ShimmerEffect( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ // Header Skeleton Row(mainAxisAlignment: MainAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 14, width: 200, color: Colors.grey.shade300), MySpacing.height(4), Container( height: 10, width: 150, color: Colors.grey.shade300), ])) ]), const SizedBox(height: 16), // Total Value Card Skeleton Container( padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12), decoration: BoxDecoration( color: Colors.grey.shade200, // Simulated light blue background borderRadius: BorderRadius.circular(5), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( height: 12, width: 160, color: Colors.grey.shade300), Icon(Icons.account_balance_wallet_outlined, color: Colors.grey.shade300, size: 20), ], ), MySpacing.height(8), Container( height: 16, width: 120, color: Colors.grey.shade300), MySpacing.height(4), Container( height: 12, width: 180, color: Colors.grey.shade300), ], ), ), const SizedBox(height: 16), // Condensed Metrics Row Skeleton Row( children: List.generate( 3, (index) => Expanded( child: Padding( padding: index == 1 ? MySpacing.symmetric(horizontal: 8) : EdgeInsets.zero, child: Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 12), decoration: BoxDecoration( color: Colors.grey.shade200, // Card background borderRadius: BorderRadius.circular(5), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.circle, color: Colors.grey.shade300, size: 16), MySpacing.width(4), Container( height: 10, width: 50, color: Colors.grey.shade300), ], ), MySpacing.height(6), Container( height: 14, width: 80, color: Colors.grey.shade300), Container( height: 10, width: 60, color: Colors.grey.shade300), ], ), ), ), )), ), const SizedBox(height: 16), const Divider( height: 1, thickness: 0.5, color: Colors.transparent), // Hidden divider for spacing const SizedBox(height: 16), // Status Breakdown Section Skeleton (Chart + Legend) Container(height: 12, width: 180, color: Colors.grey.shade300), const SizedBox(height: 8), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Donut Chart Placeholder Container( width: 120, height: 120, alignment: Alignment.center, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all(color: Colors.grey.shade300, width: 6), color: Colors.grey.shade200, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( height: 10, width: 60, color: Colors.grey.shade300), MySpacing.height(4), Container( height: 14, width: 40, color: Colors.grey.shade300), ], ), ), const SizedBox(width: 16), // Legend/Details Placeholder Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: List.generate( 3, (index) => Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Container( width: 8, height: 8, margin: const EdgeInsets.only(right: 8), decoration: BoxDecoration( color: Colors.grey.shade300, shape: BoxShape.circle)), Container( height: 12, width: 80, color: Colors.grey.shade300), ], ), Container( height: 14, width: 50, color: Colors.grey.shade300), ], ), )), ), ), ], ), const SizedBox(height: 16), const Divider( height: 1, thickness: 0.5, color: Colors.transparent), // Hidden divider for spacing const SizedBox(height: 16), // Top Projects Section Skeleton Container(height: 12, width: 200, color: Colors.grey.shade300), const SizedBox(height: 8), Column( children: List.generate( 3, (index) => Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( width: 6, height: 6, margin: const EdgeInsets.only(right: 8), decoration: BoxDecoration( color: Colors.grey.shade300, shape: BoxShape.circle)), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 12, width: double.infinity, color: Colors.grey.shade300), MySpacing.height(4), ClipRRect( borderRadius: BorderRadius.circular(5), child: Container( height: 4, color: Colors.grey.shade300), ), ], ), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Container( height: 14, width: 70, color: Colors.grey.shade300), MySpacing.height(2), Container( height: 10, width: 40, color: Colors.grey.shade300), ], ), ], ), )), ), ], ), ), ); } } /// A custom reusable Shimmer Effect widget. /// This creates a gradient wave animation over any child widget. class ShimmerEffect extends StatefulWidget { final Widget child; final Color baseColor; final Color highlightColor; ShimmerEffect({ Key? key, required this.child, Color? baseColor, Color? highlightColor, }) : baseColor = baseColor ?? Colors.grey.shade300, highlightColor = highlightColor ?? Colors.grey.shade100, super(key: key); @override State createState() => _ShimmerEffectState(); } class _ShimmerEffectState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(seconds: 2), // Adjust speed here )..repeat(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, child: widget.child, builder: (context, child) { return ShaderMask( blendMode: BlendMode.srcATop, shaderCallback: (bounds) { final gradient = LinearGradient( colors: [ widget.baseColor, widget.highlightColor, widget.baseColor, ], stops: const [0.1, 0.5, 0.9], begin: const Alignment(-1.0, -0.3), end: const Alignment(1.0, 0.3), transform: _SlidingGradientTransform(_controller.value), ); return gradient.createShader(bounds); }, child: child, ); }, ); } } class _SlidingGradientTransform extends GradientTransform { final double percent; const _SlidingGradientTransform(this.percent); @override Matrix4? transform(Rect bounds, {TextDirection? textDirection}) { // This moves the gradient across the width of the widget return Matrix4.translationValues( (bounds.width * 2) * percent - bounds.width, 0, 0); } }