Files
lis/mylis/lib/screens/expertise/expertise_screen.dart
T
2026-07-21 19:08:27 +07:00

392 lines
14 KiB
Dart

part of '../../app/app.dart';
class ExpertiseScreen extends StatefulWidget {
const ExpertiseScreen({
super.key,
required this.token,
required this.baseUrl,
required this.id,
});
final String token;
final String baseUrl;
final int id;
@override
State<ExpertiseScreen> createState() => _ExpertiseScreenState();
}
class _ExpertiseScreenState extends State<ExpertiseScreen> {
late final ApiClient _api;
late Future<Map<String, dynamic>> _future;
final Map<String, TextEditingController> _textControllers = {};
final Map<String, String> _selectValues = {};
final Map<String, Set<String>> _multiValues = {};
final Map<String, String> _staffValues = {};
String _currentDlp = '';
bool _criticalValue = false;
bool _saving = false;
@override
void initState() {
super.initState();
_api = ApiClient(baseUrl: widget.baseUrl, token: widget.token);
_future = _load();
}
@override
void dispose() {
for (final controller in _textControllers.values) {
controller.dispose();
}
super.dispose();
}
Future<Map<String, dynamic>> _load() {
return _api.get('api/mobile/examinations/${widget.id}/expertise');
}
void _reload() {
setState(() => _future = _load());
}
void _prepareFields(Map<String, dynamic> data) {
final dlp = data['dlp']?.toString() ?? '';
if (_currentDlp == dlp && _textControllers.isNotEmpty) {
return;
}
_currentDlp = dlp;
for (final controller in _textControllers.values) {
controller.dispose();
}
_textControllers.clear();
_selectValues.clear();
_multiValues.clear();
_staffValues.clear();
final components = asMap(data['components']);
final item = asMap(data['item']);
final user = asMap(data['user']);
_staffValues['analis'] = (item['analis'] ?? user['id'] ?? '0').toString();
_staffValues['ppds3'] = (item['ppds3'] ?? user['id'] ?? '0').toString();
_staffValues['dokter'] = (item['dokter_id'] ?? user['id'] ?? '0')
.toString();
_criticalValue = data['is_critical'] == true;
for (final rawField in asList(data['fields'])) {
final field = asMap(rawField);
final name = field['name']?.toString() ?? '';
final type = field['type']?.toString() ?? 'text';
final value = components[name];
if (type == 'select') {
_selectValues[name] = value?.toString() ?? '';
} else if (type == 'multiselect') {
_multiValues[name] = value is List
? value.map((item) => item.toString()).toSet()
: <String>{};
} else {
_textControllers[name] = TextEditingController(
text: value?.toString() ?? '',
);
}
}
}
Future<void> _setTemplate(String dlp) async {
setState(() => _saving = true);
try {
await _api.post(
'api/mobile/examinations/${widget.id}/expertise/template',
{'dlp': dlp},
);
if (!mounted) return;
_showMessage('Template $dlp dipilih.');
setState(() => _future = _load());
} on ApiException catch (error) {
_showMessage(error.message);
} catch (_) {
_showMessage('Template tidak bisa dipilih.');
} finally {
if (mounted) {
setState(() => _saving = false);
}
}
}
Future<void> _save(String action) async {
setState(() => _saving = true);
final fields = <String, dynamic>{};
for (final entry in _textControllers.entries) {
fields[entry.key] = entry.value.text.trim();
}
for (final entry in _selectValues.entries) {
fields[entry.key] = entry.value;
}
for (final entry in _multiValues.entries) {
fields[entry.key] = entry.value.toList();
}
try {
final data = await _api
.post('api/mobile/examinations/${widget.id}/expertise', {
'dlp': _currentDlp,
'action': action,
'fields': fields,
'keterangan': fields['keterangan']?.toString() ?? '',
'analis': _staffValues['analis'] ?? '0',
'ppds3': _staffValues['ppds3'] ?? '0',
'dokter': _staffValues['dokter'] ?? '0',
'nilai_kritis': _criticalValue ? '1' : '0',
});
if (!mounted) return;
_showMessage(data['message']?.toString() ?? 'Expertise tersimpan.');
setState(() => _future = _load());
} on ApiException catch (error) {
_showMessage(error.message);
} catch (_) {
_showMessage('Expertise tidak bisa disimpan.');
} finally {
if (mounted) {
setState(() => _saving = false);
}
}
}
void _showMessage(String message) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(message)));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Expertise'),
actions: [
IconButton(
onPressed: _saving ? null : _reload,
icon: const Icon(Icons.refresh),
tooltip: 'Refresh',
),
],
),
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']);
final user = asMap(data['user']);
_prepareFields(data);
final isSupervisor = _isSupervisor(user['previlage']?.toString());
return ListView(
padding: const EdgeInsets.all(16),
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item['nmpasien']?.toString() ?? '-',
style: Theme.of(context).textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.w800),
),
const SizedBox(height: 6),
Text(
'${item['nofoto'] ?? '-'} / RM ${item['noregister'] ?? '-'}',
),
Text(item['reques']?.toString() ?? '-'),
const SizedBox(height: 8),
Row(
children: [
StatusPill(
status: item['status']?.toString() ?? 'NEW',
),
const SizedBox(width: 8),
if (_currentDlp.isNotEmpty)
BadgeLabel(
text: _currentDlp,
color: const Color(0xFF0F766E),
),
],
),
],
),
),
),
const SizedBox(height: 14),
if (_currentDlp.isEmpty)
TemplatePicker(
templates: asList(data['templates']),
saving: _saving,
onUse: _setTemplate,
)
else
_currentDlp == 'CCI'
? CciExpertiseWizard(
item: item,
user: user,
staffOptions: asMap(data['staff_options']),
optionSets: asMap(data['option_sets']),
textControllers: _textControllers,
selectValues: _selectValues,
staffValues: _staffValues,
criticalValue: _criticalValue,
isSupervisor: isSupervisor,
saving: _saving,
onCriticalChanged: (value) =>
setState(() => _criticalValue = value),
onChanged: () => setState(() {}),
onSave: _save,
)
: _currentDlp == 'Kultur'
? KulturExpertiseWizard(
item: item,
user: user,
staffOptions: asMap(data['staff_options']),
optionSets: asMap(data['option_sets']),
textControllers: _textControllers,
selectValues: _selectValues,
multiValues: _multiValues,
staffValues: _staffValues,
criticalValue: _criticalValue,
isSupervisor: isSupervisor,
saving: _saving,
onCriticalChanged: (value) =>
setState(() => _criticalValue = value),
onChanged: () => setState(() {}),
onSave: _save,
)
: _currentDlp == 'Pewarna Langsung'
? PewarnaanLangsungExpertiseWizard(
item: item,
user: user,
staffOptions: asMap(data['staff_options']),
optionSets: asMap(data['option_sets']),
textControllers: _textControllers,
selectValues: _selectValues,
multiValues: _multiValues,
staffValues: _staffValues,
criticalValue: _criticalValue,
isSupervisor: isSupervisor,
saving: _saving,
onCriticalChanged: (value) =>
setState(() => _criticalValue = value),
onChanged: () => setState(() {}),
onSave: _save,
)
: _currentDlp == 'Viral Load'
? ViralLoadExpertiseWizard(
item: item,
user: user,
staffOptions: asMap(data['staff_options']),
optionSets: asMap(data['option_sets']),
textControllers: _textControllers,
selectValues: _selectValues,
staffValues: _staffValues,
criticalValue: _criticalValue,
isSupervisor: isSupervisor,
saving: _saving,
onCriticalChanged: (value) =>
setState(() => _criticalValue = value),
onChanged: () => setState(() {}),
onSave: _save,
)
: _currentDlp == 'IgM IgG Leptospira'
? LeptospiraExpertiseWizard(
item: item,
user: user,
staffOptions: asMap(data['staff_options']),
textControllers: _textControllers,
selectValues: _selectValues,
staffValues: _staffValues,
criticalValue: _criticalValue,
isSupervisor: isSupervisor,
saving: _saving,
onCriticalChanged: (value) =>
setState(() => _criticalValue = value),
onChanged: () => setState(() {}),
onSave: _save,
)
: _currentDlp == 'PCR COVID'
? CovidExpertiseWizard(
item: item,
user: user,
staffOptions: asMap(data['staff_options']),
textControllers: _textControllers,
selectValues: _selectValues,
staffValues: _staffValues,
criticalValue: _criticalValue,
isSupervisor: isSupervisor,
saving: _saving,
onCriticalChanged: (value) =>
setState(() => _criticalValue = value),
onChanged: () => setState(() {}),
onSave: _save,
)
: _currentDlp == 'TBC'
? TbcExpertiseWizard(
item: item,
user: user,
staffOptions: asMap(data['staff_options']),
textControllers: _textControllers,
selectValues: _selectValues,
multiValues: _multiValues,
staffValues: _staffValues,
criticalValue: _criticalValue,
isSupervisor: isSupervisor,
saving: _saving,
onCriticalChanged: (value) =>
setState(() => _criticalValue = value),
onChanged: () => setState(() {}),
onSave: _save,
)
: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
PatientStaffPanel(
item: item,
user: user,
staffOptions: asMap(data['staff_options']),
staffValues: _staffValues,
onChanged: () => setState(() {}),
),
const SizedBox(height: 12),
ExpertiseForm(
dlp: _currentDlp,
fields: asList(data['fields']),
textControllers: _textControllers,
selectValues: _selectValues,
multiValues: _multiValues,
onChanged: () => setState(() {}),
),
const SizedBox(height: 12),
ExpertiseActions(
isSupervisor: isSupervisor,
saving: _saving,
onSave: _save,
),
],
),
],
);
},
),
);
}
}