part of '../../app/app.dart'; class _DashboardSubHeader extends StatelessWidget { const _DashboardSubHeader({ required this.title, required this.subtitle, required this.badgeText, required this.user, required this.notificationCount, required this.onNotifications, required this.onProfile, }); final String title; final String subtitle; final String badgeText; final Map user; final int notificationCount; final VoidCallback onNotifications; final VoidCallback onProfile; @override Widget build(BuildContext context) { return Row( children: [ IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.arrow_back_ios_new_rounded), color: const Color(0xFF080D3D), ), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Flexible( child: Text( title, maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.headlineSmall ?.copyWith( color: const Color(0xFF080D3D), fontWeight: FontWeight.w900, ), ), ), const SizedBox(width: 8), BadgeLabel(text: badgeText, color: const Color(0xFF0B7BFF)), ], ), const SizedBox(height: 4), Text( subtitle, style: Theme.of(context).textTheme.titleMedium?.copyWith( color: const Color(0xFF47517A), fontWeight: FontWeight.w700, ), ), ], ), ), Stack( clipBehavior: Clip.none, children: [ IconButton( onPressed: onNotifications, icon: const Icon(Icons.notifications_none_rounded, size: 30), color: const Color(0xFF063B60), tooltip: 'Notifikasi nilai kritis', ), if (notificationCount > 0) Positioned( top: 6, right: 6, child: Container( padding: const EdgeInsets.all(5), constraints: const BoxConstraints(minWidth: 20), decoration: const BoxDecoration( color: Color(0xFFFF1D25), shape: BoxShape.circle, ), child: Text( notificationCount > 99 ? '99+' : '$notificationCount', textAlign: TextAlign.center, style: const TextStyle( color: Colors.white, fontSize: 11, fontWeight: FontWeight.w800, ), ), ), ), ], ), const SizedBox(width: 10), InkWell( borderRadius: BorderRadius.circular(28), onTap: onProfile, child: CircleAvatar( radius: 27, backgroundColor: const Color(0xFF12BFA5), child: Text( _initials(user['nama']?.toString() ?? 'SP'), style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w900, ), ), ), ), ], ); } String _initials(String name) { final parts = name .trim() .split(RegExp(r'\s+')) .where((part) => part.isNotEmpty) .toList(); if (parts.isEmpty) { return 'SP'; } return parts.take(2).map((part) => part[0].toUpperCase()).join(); } } class SampleGroupsScreen extends StatelessWidget { const SampleGroupsScreen({ super.key, required this.books, required this.token, required this.baseUrl, required this.user, required this.notificationCount, }); final List books; final String token; final String baseUrl; final Map user; final int notificationCount; @override Widget build(BuildContext context) { final total = books.fold( 0, (sum, item) => sum + (asMap(item)['total'] as num? ?? 0).toInt(), ); return Scaffold( body: ListView( padding: const EdgeInsets.fromLTRB(18, 18, 18, 24), children: [ _DashboardSubHeader( title: 'Detail Sampel', subtitle: 'Catatan & Register', badgeText: '$total', user: user, notificationCount: notificationCount, onNotifications: () { Navigator.of(context).push( MaterialPageRoute( builder: (_) => CriticalValuesScreen(token: token, baseUrl: baseUrl), ), ); }, onProfile: () => _showProfile(context, user), ), const SizedBox(height: 28), _SampleGroupsSummary(total: total, count: books.length), const SizedBox(height: 18), Text( 'Jenis Spesimen', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w900, color: const Color(0xFF080D3D), ), ), const SizedBox(height: 12), if (books.isEmpty) const EmptyPanel(text: 'Tidak ada kelompok pemeriksaan.') else ...books.map( (book) => _SampleGroupCard( book: asMap(book), onTap: () { final mapped = asMap(book); Navigator.of(context).push( MaterialPageRoute( builder: (_) => SpecimenRegisterScreen( token: token, baseUrl: baseUrl, master: mapped['master']?.toString() ?? 'buku0', title: mapped['label']?.toString() ?? 'Spesimen', total: (mapped['total'] as num? ?? 0).toInt(), user: user, notificationCount: notificationCount, ), ), ); }, ), ), ], ), ); } void _showProfile(BuildContext context, Map user) { showModalBottomSheet( context: context, showDragHandle: true, builder: (context) => SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(20, 4, 20, 20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ CircleAvatar( radius: 28, backgroundColor: const Color(0xFF0F766E), child: Text( _initials(user['nama']?.toString() ?? 'SP'), style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w900, ), ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( user['nama']?.toString() ?? '-', style: Theme.of(context).textTheme.titleMedium ?.copyWith(fontWeight: FontWeight.w900), ), const SizedBox(height: 2), Text( '${user['username'] ?? '-'} - ${user['previlage'] ?? '-'}', style: const TextStyle(color: Colors.black54), ), ], ), ), ], ), const SizedBox(height: 18), FilledButton.icon( onPressed: () async { await SessionStore.clear(); if (!context.mounted) return; Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder: (_) => const LoginScreen()), (_) => false, ); }, icon: const Icon(Icons.logout), label: const Text('Logout'), ), ], ), ), ), ); } String _initials(String name) { final parts = name .trim() .split(RegExp(r'\s+')) .where((part) => part.isNotEmpty) .toList(); if (parts.isEmpty) { return 'SP'; } return parts.take(2).map((part) => part[0].toUpperCase()).join(); } } class _SampleGroupsSummary extends StatelessWidget { const _SampleGroupsSummary({required this.total, required this.count}); final int total; final int count; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: const Color(0xFFE1E8F5)), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: const Color(0xFF2563EB).withValues(alpha: 0.05), blurRadius: 18, offset: const Offset(0, 10), ), ], ), child: Row( children: [ Container( width: 58, height: 58, decoration: BoxDecoration( color: const Color(0xFFFF6B00).withValues(alpha: 0.12), borderRadius: BorderRadius.circular(16), ), child: const Icon( Icons.menu_book_rounded, color: Color(0xFFFF6B00), size: 32, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '$total sampel aktif', style: Theme.of(context).textTheme.titleLarge?.copyWith( color: const Color(0xFF080D3D), fontWeight: FontWeight.w900, ), ), const SizedBox(height: 4), Text( '$count jenis spesimen tersedia', style: const TextStyle( color: Color(0xFF47517A), fontWeight: FontWeight.w700, ), ), ], ), ), ], ), ); } } class _SampleGroupCard extends StatelessWidget { const _SampleGroupCard({required this.book, required this.onTap}); final Map book; final VoidCallback onTap; @override Widget build(BuildContext context) { final total = (book['total'] as num? ?? 0).toInt(); return Card( margin: const EdgeInsets.only(bottom: 12), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), side: const BorderSide(color: Color(0xFFE1E8F5)), ), child: InkWell( borderRadius: BorderRadius.circular(14), onTap: onTap, child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Container( width: 54, height: 54, decoration: BoxDecoration( color: const Color(0xFFFF6B00).withValues(alpha: 0.10), shape: BoxShape.circle, ), child: const Icon( Icons.science_outlined, color: Color(0xFFFF6B00), size: 28, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( book['label']?.toString() ?? 'Spesimen', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: const Color(0xFF080D3D), fontWeight: FontWeight.w900, ), ), const SizedBox(height: 6), BadgeLabel( text: '${book['master'] ?? '-'}', color: const Color(0xFFFF6B00), ), ], ), ), BadgeLabel(text: '$total sampel', color: const Color(0xFF0B7BFF)), const SizedBox(width: 8), const Icon(Icons.chevron_right_rounded, color: Color(0xFF667095)), ], ), ), ), ); } }