Files
lis/mylis/lib/screens/examinations/examination_detail_screen.dart
T
2026-08-02 10:46:34 +07:00

278 lines
7.9 KiB
Dart

part of '../../app/app.dart';
class ExaminationDetailScreen extends StatefulWidget {
const ExaminationDetailScreen({
super.key,
required this.token,
required this.baseUrl,
required this.id,
});
final String token;
final String baseUrl;
final int id;
@override
State<ExaminationDetailScreen> createState() =>
_ExaminationDetailScreenState();
}
class _ExaminationDetailScreenState extends State<ExaminationDetailScreen> {
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/examinations/${widget.id}');
}
void _reload() {
setState(() {
_future = _api.get('api/mobile/examinations/${widget.id}');
});
}
Future<void> _launch(String? url) async {
if (url == null || url.isEmpty) {
return;
}
final uri = Uri.parse(url);
if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
if (!mounted) {
return;
}
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Halaman tidak bisa dibuka.')),
);
}
}
Future<void> _openExpertise() async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ExpertiseScreen(
token: widget.token,
baseUrl: widget.baseUrl,
id: widget.id,
),
),
);
_reload();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: 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 item = asMap(data['item']);
return ListView(
padding: const EdgeInsets.fromLTRB(18, 18, 18, 28),
children: [
SafeArea(
bottom: false,
child: _ExaminationScreenHeader(
title: 'Detail Pemeriksaan',
subtitle: item['nofoto']?.toString() ?? '-',
onRefresh: _reload,
),
),
const SizedBox(height: 22),
_ExaminationPatientCard(item: item),
const SizedBox(height: 14),
_ExaminationInfoCard(item: item),
const SizedBox(height: 14),
_ExaminationActionPanel(
onExpertise: _openExpertise,
onRefresh: _reload,
resultUrl: data['result_url']?.toString(),
onPreview: () => _launch(data['result_url']?.toString()),
),
],
);
},
),
);
}
}
class _ExaminationPatientCard extends StatelessWidget {
const _ExaminationPatientCard({required this.item});
final Map<String, dynamic> item;
@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: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
item['nmpasien']?.toString() ?? '-',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: const Color(0xFF080D3D),
fontWeight: FontWeight.w900,
),
),
),
const SizedBox(width: 10),
StatusPill(status: item['status']?.toString() ?? 'NEW'),
],
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
BadgeLabel(
text: item['nofoto']?.toString() ?? '-',
color: const Color(0xFF0B7BFF),
),
BadgeLabel(
text: 'RM ${item['noregister'] ?? '-'}',
color: const Color(0xFF667095),
),
BadgeLabel(
text: item['nm_spesimen']?.toString() ?? '-',
color: const Color(0xFFFF6B00),
),
],
),
const SizedBox(height: 12),
Text(
item['reques']?.toString() ?? '-',
style: const TextStyle(
color: Color(0xFF47517A),
fontWeight: FontWeight.w800,
height: 1.35,
),
),
],
),
);
}
}
class _ExaminationInfoCard extends StatelessWidget {
const _ExaminationInfoCard({required this.item});
final Map<String, dynamic> item;
@override
Widget build(BuildContext context) {
final rows = [
('Asal Pasien', item['asalpasien']),
('Ruangan', item['ruangan']),
('Dokter Pengirim', item['klinisi'] ?? item['nmdokter']),
('Tanggal Daftar', item['daftar']),
('Tanggal Sampel', item['tanggalsampel']),
('Cara Pengambilan', item['pengambilan']),
('Asal Pengambilan', item['asalpengirim']),
('Alamat', item['alamatpasien']),
];
return Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: const Color(0xFFE1E8F5)),
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Informasi Pemeriksaan',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: const Color(0xFF080D3D),
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 14),
...rows.map((row) => DetailRow(label: row.$1, value: row.$2)),
],
),
);
}
}
class _ExaminationActionPanel extends StatelessWidget {
const _ExaminationActionPanel({
required this.onExpertise,
required this.onRefresh,
required this.resultUrl,
required this.onPreview,
});
final VoidCallback onExpertise;
final VoidCallback onRefresh;
final String? resultUrl;
final VoidCallback onPreview;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: const Color(0xFFE1E8F5)),
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
FilledButton.icon(
onPressed: onExpertise,
icon: const Icon(Icons.edit_document),
label: const Text('Expertise'),
),
const SizedBox(height: 10),
OutlinedButton.icon(
onPressed: onRefresh,
icon: const Icon(Icons.fact_check_outlined),
label: const Text('Cek Status'),
),
if (resultUrl?.isNotEmpty == true) ...[
const SizedBox(height: 10),
TextButton.icon(
onPressed: onPreview,
icon: const Icon(Icons.description_outlined),
label: const Text('Preview Hasil'),
),
],
],
),
);
}
}