update
This commit is contained in:
@@ -4951,9 +4951,6 @@
|
||||
dlp = dlp || document.getElementById('dlp').value;
|
||||
nofoto = nofoto || document.getElementById('nofoto').value;
|
||||
idne = idne || document.getElementById('periksa_id').value;
|
||||
$('#dlp').val(dlp);
|
||||
$('#nofoto').val(nofoto);
|
||||
$('#periksa_id').val(idne);
|
||||
$('#template_jenis').val(dlp);
|
||||
$('#divtemplate').hide();
|
||||
$('.templatejawaban').hide();
|
||||
@@ -7201,8 +7198,8 @@
|
||||
$("#template_jenis").val(dataRecord.judul);
|
||||
$("#dlp").val(dataRecord.judul);
|
||||
var dlp = dataRecord.judul;
|
||||
var nofoto = document.getElementById('nofoto').value || document.getElementById('nofoto').textContent;
|
||||
var idne = document.getElementById('periksa_id').value || document.getElementById('periksa_id').textContent;
|
||||
var nofoto = document.getElementById('nofoto').value;
|
||||
var idne = document.getElementById('periksa_id').value;
|
||||
openlamanekpertise(dlp, nofoto, id);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4989,9 +4989,6 @@
|
||||
} else {
|
||||
$('#divtemplate').hide();
|
||||
}
|
||||
$('#nofoto').val(nofoto).html(nofoto);
|
||||
$('#periksa_id').val(idne);
|
||||
$('#template_jenis').val(dlp);
|
||||
$('#divreadonly').hide();
|
||||
$('#divawal').hide();
|
||||
$('#divperiksa').show();
|
||||
@@ -7251,8 +7248,8 @@
|
||||
$("#template_jenis").val(dataRecord.judul);
|
||||
$("#dlp").val(dataRecord.judul);
|
||||
var dlp = dataRecord.judul;
|
||||
var nofoto = document.getElementById('nofoto').value || document.getElementById('nofoto').textContent;
|
||||
var idne = document.getElementById('periksa_id').value || document.getElementById('periksa_id').textContent;
|
||||
var nofoto = document.getElementById('nofoto').value;
|
||||
var idne = document.getElementById('periksa_id').value;
|
||||
openlamanekpertise(dlp, nofoto, id);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -26,7 +26,9 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
||||
}
|
||||
|
||||
void _reload() {
|
||||
setState(() => _future = _api.get('api/mobile/dashboard'));
|
||||
setState(() {
|
||||
_future = _api.get('api/mobile/dashboard');
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _logout() async {
|
||||
|
||||
@@ -29,7 +29,9 @@ class _ExaminationDetailScreenState extends State<ExaminationDetailScreen> {
|
||||
}
|
||||
|
||||
void _reload() {
|
||||
setState(() => _future = _api.get('api/mobile/examinations/${widget.id}'));
|
||||
setState(() {
|
||||
_future = _api.get('api/mobile/examinations/${widget.id}');
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _launch(String? url) async {
|
||||
|
||||
@@ -45,7 +45,9 @@ class _ExaminationListScreenState extends State<ExaminationListScreen> {
|
||||
}
|
||||
|
||||
void _reload() {
|
||||
setState(() => _future = _load());
|
||||
setState(() {
|
||||
_future = _load();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -161,7 +161,11 @@ class _CciExpertiseWizardState extends State<CciExpertiseWizard> {
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_text('keterangan', 'Expertise', minLines: 8, maxLines: 12),
|
||||
ExpertiseHtmlEditor(
|
||||
controller: widget.textControllers['keterangan'] ??=
|
||||
TextEditingController(),
|
||||
label: 'Expertise',
|
||||
),
|
||||
CheckboxListTile(
|
||||
value: widget.criticalValue,
|
||||
onChanged: (value) => widget.onCriticalChanged(value ?? false),
|
||||
|
||||
@@ -177,7 +177,11 @@ class _CovidExpertiseWizardState extends State<CovidExpertiseWizard> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_text('keterangan', 'Expertise', minLines: 8, maxLines: 12),
|
||||
ExpertiseHtmlEditor(
|
||||
controller: widget.textControllers['keterangan'] ??=
|
||||
TextEditingController(),
|
||||
label: 'Expertise',
|
||||
),
|
||||
CheckboxListTile(
|
||||
value: widget.criticalValue,
|
||||
onChanged: (value) => widget.onCriticalChanged(value ?? false),
|
||||
|
||||
@@ -333,6 +333,222 @@ class CompactMultiSelectField extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class ExpertiseHtmlEditor extends StatefulWidget {
|
||||
const ExpertiseHtmlEditor({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.label = 'Expertise',
|
||||
});
|
||||
|
||||
final TextEditingController controller;
|
||||
final String label;
|
||||
|
||||
@override
|
||||
State<ExpertiseHtmlEditor> createState() => _ExpertiseHtmlEditorState();
|
||||
}
|
||||
|
||||
class _ExpertiseHtmlEditorState extends State<ExpertiseHtmlEditor> {
|
||||
bool _preview = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
widget.label,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w900),
|
||||
),
|
||||
const Spacer(),
|
||||
SegmentedButton<bool>(
|
||||
segments: const [
|
||||
ButtonSegment(value: false, label: Text('Edit')),
|
||||
ButtonSegment(value: true, label: Text('Preview')),
|
||||
],
|
||||
selected: {_preview},
|
||||
onSelectionChanged: (value) =>
|
||||
setState(() => _preview = value.first),
|
||||
showSelectedIcon: false,
|
||||
style: const ButtonStyle(
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_EditorToolbar(
|
||||
onBold: () => _wrapSelection('<b>', '</b>'),
|
||||
onItalic: () => _wrapSelection('<i>', '</i>'),
|
||||
onUnderline: () => _wrapSelection('<u>', '</u>'),
|
||||
onParagraph: () => _wrapSelection('<p>', '</p>'),
|
||||
onBullet: _insertBulletList,
|
||||
onBreak: () => _insertText('<br>'),
|
||||
onClear: _clearTags,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (_preview)
|
||||
Container(
|
||||
constraints: const BoxConstraints(minHeight: 210),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8FAFC),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
_plainPreview(widget.controller.text),
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
height: 1.45,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
TextField(
|
||||
controller: widget.controller,
|
||||
minLines: 10,
|
||||
maxLines: 16,
|
||||
keyboardType: TextInputType.multiline,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Tulis expertise...',
|
||||
border: OutlineInputBorder(),
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _wrapSelection(String open, String close) {
|
||||
final selection = widget.controller.selection;
|
||||
final text = widget.controller.text;
|
||||
if (!selection.isValid || selection.isCollapsed) {
|
||||
_insertText('$open$close', cursorOffset: open.length);
|
||||
return;
|
||||
}
|
||||
|
||||
final selectedText = text.substring(selection.start, selection.end);
|
||||
final replacement = '$open$selectedText$close';
|
||||
widget.controller.value = TextEditingValue(
|
||||
text: text.replaceRange(selection.start, selection.end, replacement),
|
||||
selection: TextSelection.collapsed(
|
||||
offset: selection.start + replacement.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _insertText(String value, {int? cursorOffset}) {
|
||||
final selection = widget.controller.selection;
|
||||
final text = widget.controller.text;
|
||||
final start = selection.isValid ? selection.start : text.length;
|
||||
final end = selection.isValid ? selection.end : text.length;
|
||||
widget.controller.value = TextEditingValue(
|
||||
text: text.replaceRange(start, end, value),
|
||||
selection: TextSelection.collapsed(
|
||||
offset: start + (cursorOffset ?? value.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _insertBulletList() {
|
||||
final selection = widget.controller.selection;
|
||||
final text = widget.controller.text;
|
||||
if (selection.isValid && !selection.isCollapsed) {
|
||||
final selectedText = text.substring(selection.start, selection.end);
|
||||
final items = selectedText
|
||||
.split('\n')
|
||||
.where((line) => line.trim().isNotEmpty)
|
||||
.map((line) => '<li>${line.trim()}</li>')
|
||||
.join();
|
||||
final replacement = '<ul>$items</ul>';
|
||||
widget.controller.value = TextEditingValue(
|
||||
text: text.replaceRange(selection.start, selection.end, replacement),
|
||||
selection: TextSelection.collapsed(
|
||||
offset: selection.start + replacement.length,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
_insertText('<ul><li></li></ul>', cursorOffset: '<ul><li>'.length);
|
||||
}
|
||||
|
||||
void _clearTags() {
|
||||
final clean = _plainPreview(widget.controller.text);
|
||||
widget.controller.value = TextEditingValue(
|
||||
text: clean,
|
||||
selection: TextSelection.collapsed(offset: clean.length),
|
||||
);
|
||||
}
|
||||
|
||||
String _plainPreview(String value) {
|
||||
return decodeHtmlEntities(
|
||||
value
|
||||
.replaceAll(RegExp(r'<\s*br\s*/?\s*>', caseSensitive: false), '\n')
|
||||
.replaceAll(RegExp(r'</\s*p\s*>', caseSensitive: false), '\n\n')
|
||||
.replaceAll(RegExp(r'<\s*li\s*>', caseSensitive: false), '• ')
|
||||
.replaceAll(RegExp(r'</\s*li\s*>', caseSensitive: false), '\n')
|
||||
.replaceAll(RegExp(r'<[^>]*>'), ''),
|
||||
).trim();
|
||||
}
|
||||
}
|
||||
|
||||
class _EditorToolbar extends StatelessWidget {
|
||||
const _EditorToolbar({
|
||||
required this.onBold,
|
||||
required this.onItalic,
|
||||
required this.onUnderline,
|
||||
required this.onParagraph,
|
||||
required this.onBullet,
|
||||
required this.onBreak,
|
||||
required this.onClear,
|
||||
});
|
||||
|
||||
final VoidCallback onBold;
|
||||
final VoidCallback onItalic;
|
||||
final VoidCallback onUnderline;
|
||||
final VoidCallback onParagraph;
|
||||
final VoidCallback onBullet;
|
||||
final VoidCallback onBreak;
|
||||
final VoidCallback onClear;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: [
|
||||
_tool(Icons.format_bold, 'Bold', onBold),
|
||||
_tool(Icons.format_italic, 'Italic', onItalic),
|
||||
_tool(Icons.format_underlined, 'Underline', onUnderline),
|
||||
_tool(Icons.notes, 'Paragraph', onParagraph),
|
||||
_tool(Icons.format_list_bulleted, 'Bullet', onBullet),
|
||||
_tool(Icons.keyboard_return, 'Line break', onBreak),
|
||||
_tool(Icons.format_clear, 'Clear', onClear),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tool(IconData icon, String tooltip, VoidCallback onPressed) {
|
||||
return IconButton.outlined(
|
||||
tooltip: tooltip,
|
||||
onPressed: onPressed,
|
||||
icon: Icon(icon),
|
||||
visualDensity: VisualDensity.compact,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AntibioticSensitivityPanel extends StatefulWidget {
|
||||
const AntibioticSensitivityPanel({
|
||||
super.key,
|
||||
@@ -1264,6 +1480,9 @@ class ExpertiseForm extends StatelessWidget {
|
||||
}
|
||||
|
||||
final controller = textControllers[name] ??= TextEditingController();
|
||||
if (name == 'keterangan') {
|
||||
return ExpertiseHtmlEditor(controller: controller, label: label);
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: TextField(
|
||||
|
||||
@@ -48,7 +48,9 @@ class _ExpertiseScreenState extends State<ExpertiseScreen> {
|
||||
}
|
||||
|
||||
void _reload() {
|
||||
setState(() => _future = _load());
|
||||
setState(() {
|
||||
_future = _load();
|
||||
});
|
||||
}
|
||||
|
||||
void _prepareFields(Map<String, dynamic> data) {
|
||||
@@ -106,7 +108,9 @@ class _ExpertiseScreenState extends State<ExpertiseScreen> {
|
||||
);
|
||||
if (!mounted) return;
|
||||
_showMessage('Template $dlp dipilih.');
|
||||
setState(() => _future = _load());
|
||||
setState(() {
|
||||
_future = _load();
|
||||
});
|
||||
} on ApiException catch (error) {
|
||||
_showMessage(error.message);
|
||||
} catch (_) {
|
||||
@@ -134,30 +138,35 @@ class _ExpertiseScreenState extends State<ExpertiseScreen> {
|
||||
fields[entry.key] = entry.value.toList();
|
||||
}
|
||||
|
||||
Map<String, dynamic> data;
|
||||
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());
|
||||
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;
|
||||
|
||||
@@ -339,7 +339,11 @@ class _KulturExpertiseWizardState extends State<KulturExpertiseWizard> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_text('keterangan', 'Expertise', minLines: 8, maxLines: 12),
|
||||
ExpertiseHtmlEditor(
|
||||
controller: widget.textControllers['keterangan'] ??=
|
||||
TextEditingController(),
|
||||
label: 'Expertise',
|
||||
),
|
||||
CheckboxListTile(
|
||||
value: widget.criticalValue,
|
||||
onChanged: (value) => widget.onCriticalChanged(value ?? false),
|
||||
|
||||
@@ -167,7 +167,11 @@ class _LeptospiraExpertiseWizardState extends State<LeptospiraExpertiseWizard> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_text('keterangan', 'Expertise', minLines: 8, maxLines: 12),
|
||||
ExpertiseHtmlEditor(
|
||||
controller: widget.textControllers['keterangan'] ??=
|
||||
TextEditingController(),
|
||||
label: 'Expertise',
|
||||
),
|
||||
CheckboxListTile(
|
||||
value: widget.criticalValue,
|
||||
onChanged: (value) => widget.onCriticalChanged(value ?? false),
|
||||
|
||||
@@ -277,7 +277,11 @@ class _PewarnaanLangsungExpertiseWizardState
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_text('keterangan', 'Expertise', minLines: 8, maxLines: 12),
|
||||
ExpertiseHtmlEditor(
|
||||
controller: widget.textControllers['keterangan'] ??=
|
||||
TextEditingController(),
|
||||
label: 'Expertise',
|
||||
),
|
||||
CheckboxListTile(
|
||||
value: widget.criticalValue,
|
||||
onChanged: (value) => widget.onCriticalChanged(value ?? false),
|
||||
|
||||
@@ -403,7 +403,11 @@ class _TbcExpertiseWizardState extends State<TbcExpertiseWizard> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_text('keterangan', 'Expertise', minLines: 8, maxLines: 12),
|
||||
ExpertiseHtmlEditor(
|
||||
controller: widget.textControllers['keterangan'] ??=
|
||||
TextEditingController(),
|
||||
label: 'Expertise',
|
||||
),
|
||||
CheckboxListTile(
|
||||
value: widget.criticalValue,
|
||||
onChanged: (value) => widget.onCriticalChanged(value ?? false),
|
||||
|
||||
@@ -180,7 +180,11 @@ class _ViralLoadExpertiseWizardState extends State<ViralLoadExpertiseWizard> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_text('keterangan', 'Expertise', minLines: 8, maxLines: 12),
|
||||
ExpertiseHtmlEditor(
|
||||
controller: widget.textControllers['keterangan'] ??=
|
||||
TextEditingController(),
|
||||
label: 'Expertise',
|
||||
),
|
||||
CheckboxListTile(
|
||||
value: widget.criticalValue,
|
||||
onChanged: (value) => widget.onCriticalChanged(value ?? false),
|
||||
|
||||
Reference in New Issue
Block a user