part of '../../app/app.dart'; class DashboardScreen extends StatefulWidget { const DashboardScreen({ super.key, required this.token, required this.baseUrl, }); final String token; final String baseUrl; @override State createState() => _DashboardScreenState(); } class _DashboardScreenState extends State { late final ApiClient _api; late Future> _future; @override void initState() { super.initState(); _api = ApiClient(baseUrl: widget.baseUrl, token: widget.token); _future = _api.get('api/mobile/dashboard'); } void _reload() { setState(() { _future = _api.get('api/mobile/dashboard'); }); } Future _logout() async { await SessionStore.clear(); if (!mounted) { return; } Navigator.of( context, ).pushReplacement(MaterialPageRoute(builder: (_) => const LoginScreen())); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text(kAppShortName), actions: [ IconButton( onPressed: _reload, icon: const Icon(Icons.refresh), tooltip: 'Refresh', ), IconButton( onPressed: _logout, icon: const Icon(Icons.logout), tooltip: 'Logout', ), ], ), body: RefreshIndicator( onRefresh: () async => _reload(), child: FutureBuilder>( future: _future, builder: (context, snapshot) { if (snapshot.connectionState != ConnectionState.done) { return const LoadingScreen(); } if (snapshot.hasError) { return ErrorView( message: _message(snapshot.error), onRetry: _reload, ); } final data = snapshot.data!; final user = asMap(data['user']); final summary = asMap(data['summary']); final warnings = asList(data['early_warning_groups']); final books = asList(data['books']); return ListView( padding: const EdgeInsets.fromLTRB(16, 8, 16, 24), children: [ HeaderPanel(user: user, summary: summary), const SizedBox(height: 16), SectionTitle( title: 'Early Warning Sistem', actionLabel: '${warnings.fold(0, (total, item) => total + (asMap(item)['total'] as num? ?? 0).toInt())} kasus', ), if (warnings.isEmpty) const EmptyPanel(text: 'Tidak ada data Early Warning.') else ...warnings.map( (group) => EarlyWarningGroupCard( group: asMap(group), token: widget.token, baseUrl: widget.baseUrl, ), ), const SizedBox(height: 18), const SectionTitle(title: 'List Pemeriksaan'), ...books.map( (book) => BookTile( book: asMap(book), token: widget.token, baseUrl: widget.baseUrl, ), ), ], ); }, ), ), ); } } class HeaderPanel extends StatelessWidget { const HeaderPanel({super.key, required this.user, required this.summary}); final Map user; final Map summary; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF0F766E), borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 54, height: 54, padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), ), child: Image.asset(kHospitalLogoAsset, fit: BoxFit.contain), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( kAppShortName, style: Theme.of(context).textTheme.titleLarge?.copyWith( color: Colors.white, fontWeight: FontWeight.w800, ), ), const Text( kHospitalName, style: TextStyle(color: Colors.white70, fontSize: 12), ), ], ), ), ], ), const SizedBox(height: 14), Text( user['nama']?.toString() ?? '-', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: Colors.white, fontWeight: FontWeight.w700, ), ), Text( user['previlage']?.toString() ?? '-', style: const TextStyle(color: Colors.white70), ), const SizedBox(height: 16), Row( children: [ Expanded( child: SummaryItem(label: 'PPDS', value: summary['total_ppds']), ), Expanded( child: SummaryItem( label: 'Verifikasi', value: summary['butuh_verifikasi'], ), ), Expanded( child: SummaryItem( label: 'Hari Ini', value: summary['antrian_hari_ini'], ), ), ], ), ], ), ); } } class SummaryItem extends StatelessWidget { const SummaryItem({super.key, required this.label, required this.value}); final String label; final Object? value; @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${value ?? 0}', style: Theme.of(context).textTheme.titleLarge?.copyWith( color: Colors.white, fontWeight: FontWeight.w800, ), ), Text( label, style: const TextStyle(color: Colors.white70, fontSize: 12), ), ], ); } } class SectionTitle extends StatelessWidget { const SectionTitle({super.key, required this.title, this.actionLabel}); final String title; final String? actionLabel; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( children: [ Expanded( child: Text( title, style: Theme.of( context, ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w800), ), ), if (actionLabel != null) BadgeLabel(text: actionLabel!, color: const Color(0xFFDC2626)), ], ), ); } } class EarlyWarningGroupCard extends StatelessWidget { const EarlyWarningGroupCard({ super.key, required this.group, required this.token, required this.baseUrl, }); final Map group; final String token; final String baseUrl; @override Widget build(BuildContext context) { final items = asList(group['items']); return Card( margin: const EdgeInsets.only(bottom: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), child: ExpansionTile( leading: const Icon( Icons.warning_amber_rounded, color: Color(0xFFDC2626), ), title: Text( group['subpoli']?.toString() ?? 'Tanpa Subpoli', style: const TextStyle(fontWeight: FontWeight.w700), ), subtitle: Text('${group['total'] ?? items.length} kasus warning'), children: items .map( (item) => ExaminationCompactTile( item: asMap(item), token: token, baseUrl: baseUrl, ), ) .toList(), ), ); } } class BookTile extends StatelessWidget { const BookTile({ super.key, required this.book, required this.token, required this.baseUrl, }); final Map book; final String token; final String baseUrl; @override Widget build(BuildContext context) { return Card( margin: const EdgeInsets.only(bottom: 8), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), child: ListTile( leading: const CircleAvatar( backgroundColor: Color(0xFFE0F2F1), child: Icon(Icons.science_outlined, color: Color(0xFF0F766E)), ), title: Text(book['label']?.toString() ?? '-'), subtitle: Text('${book['total'] ?? 0} pemeriksaan aktif'), trailing: const Icon(Icons.chevron_right), onTap: () { Navigator.of(context).push( MaterialPageRoute( builder: (_) => ExaminationListScreen( token: token, baseUrl: baseUrl, master: book['master'].toString(), title: book['label']?.toString() ?? 'Pemeriksaan', ), ), ); }, ), ); } }