Files
lis/mylis/lib/screens/expertise/expertise_screen.dart
T
2026-07-22 14:06:01 +07:00

453 lines
16 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 = {};
final Map<String, int> _wizardSteps = {};
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() ?? '',
);
}
}
for (final name in kAntibioticMediaRowFields) {
_textControllers[name] = TextEditingController(
text: components[name]?.toString() ?? '',
);
}
for (final name in kToolAntibioticRowFields) {
_textControllers[name] = TextEditingController(
text: components[name]?.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 {
if (action == 'statuspewarnaanlsg') {
_wizardSteps[_currentDlp] = 1;
}
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();
}
Map<String, dynamic> data;
try {
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',
});
} on ApiException catch (error) {
_showMessage(error.message);
return;
} catch (_) {
_showMessage('Expertise tidak bisa disimpan.');
return;
} finally {
if (mounted) {
setState(() => _saving = false);
}
}
if (!mounted) return;
_showMessage(data['message']?.toString() ?? 'Expertise tersimpan.');
setState(() {
_future = _load();
});
}
int _wizardStep(String dlp) => _wizardSteps[dlp] ?? 0;
void _setWizardStep(String dlp, int step) {
_wizardSteps[dlp] = step;
}
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());
final summaryCard = 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),
),
],
),
],
),
),
);
final expertiseContent = _currentDlp.isEmpty
? TemplatePicker(
templates: asList(data['templates']),
saving: _saving,
onUse: _setTemplate,
)
: _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,
initialStep: _wizardStep(_currentDlp),
onStepChanged: (step) => _setWizardStep(_currentDlp, step),
)
: _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,
initialStep: _wizardStep(_currentDlp),
onStepChanged: (step) => _setWizardStep(_currentDlp, step),
)
: _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,
initialStep: _wizardStep(_currentDlp),
onStepChanged: (step) => _setWizardStep(_currentDlp, step),
)
: _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,
initialStep: _wizardStep(_currentDlp),
onStepChanged: (step) => _setWizardStep(_currentDlp, step),
)
: _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,
initialStep: _wizardStep(_currentDlp),
onStepChanged: (step) => _setWizardStep(_currentDlp, step),
)
: _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,
initialStep: _wizardStep(_currentDlp),
onStepChanged: (step) => _setWizardStep(_currentDlp, step),
)
: _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,
initialStep: _wizardStep(_currentDlp),
onStepChanged: (step) => _setWizardStep(_currentDlp, step),
)
: SingleChildScrollView(
padding: const EdgeInsets.only(bottom: 16),
child: 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,
),
],
),
);
if (_currentDlp.isEmpty) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
summaryCard,
const SizedBox(height: 14),
expertiseContent,
],
);
}
return Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
child: Column(
children: [
summaryCard,
const SizedBox(height: 14),
Expanded(child: expertiseContent),
],
),
);
},
),
);
}
}