342 lines
9.7 KiB
Dart
342 lines
9.7 KiB
Dart
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<DashboardScreen> createState() => _DashboardScreenState();
|
|
}
|
|
|
|
class _DashboardScreenState extends State<DashboardScreen> {
|
|
late final ApiClient _api;
|
|
late Future<Map<String, dynamic>> _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<void> _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<Map<String, dynamic>>(
|
|
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<int>(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<String, dynamic> user;
|
|
final Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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',
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|