This commit is contained in:
Dwi Swandhana
2026-08-04 05:55:29 +07:00
parent 2bf4cac343
commit d369d3754b
15 changed files with 936 additions and 354 deletions

No files matched your search

Vendored
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Vendored Regular → Executable
View File
Regular → Executable
View File
View File
Regular → Executable
+87 -104
View File
@@ -158,7 +158,7 @@ File: Main Css File
} }
#topnav .navbar-custom { #topnav .navbar-custom {
background-color: #FEF0E1; background-color: #02c0ce;
} }
#topnav .navbar-toggle { #topnav .navbar-toggle {
@@ -174,7 +174,7 @@ File: Main Css File
} }
#topnav .navbar-toggle:hover span { #topnav .navbar-toggle:hover span {
background-color: #FEF0E1; background-color: #313a46;
} }
#topnav .navbar-toggle:focus { #topnav .navbar-toggle:focus {
@@ -182,7 +182,7 @@ File: Main Css File
} }
#topnav .navbar-toggle:focus span { #topnav .navbar-toggle:focus span {
background-color: #FEF0E1; background-color: #313a46;
} }
#topnav .navbar-toggle .lines { #topnav .navbar-toggle .lines {
@@ -240,72 +240,40 @@ File: Main Css File
} }
#topnav .navigation-menu > li > a { #topnav .navigation-menu > li > a {
display: flex; /* Menggunakan Flexbox untuk alignment */ display: block;
align-items: center; /* Membuat item (ikon + teks) rata tengah secara vertikal */ color: rgba(255, 255, 255, 0.7);
justify-content: center; /* Membuat item (ikon + teks) rata tengah secara horizontal */
color: #2F2F2F;
font-weight: 500; font-weight: 500;
font-size: 16px; font-size: 16px;
-webkit-transition: all .3s ease; -webkit-transition: all .5s ease;
transition: all .3s ease; transition: all .5s ease;
line-height: 20px; line-height: 20px;
padding: 20px 25px; padding-left: 25px;
border-radius: 10px; padding-right: 25px;
min-width: 130px; /* Atur lebar minimum untuk semua menu */
} }
#topnav .navigation-menu > li > a:hover {
color: #ffffff;
}
#topnav .navigation-menu > li > a:focus {
color: #ffffff;
}
#topnav .navigation-menu > li > a:active {
color: #ffffff;
}
#topnav .navigation-menu > li > a i { #topnav .navigation-menu > li > a i {
margin-right: 8px; /* Memberi jarak antara ikon dan teks */
}
#topnav .navigation-menu > li > a:hover,
#topnav .navigation-menu > li.active > a {
background-color: #FFA14F; /* Warna menu di sorot dan aktif */
color: #2F2F2F; /* Warna font menu di sorot dan aktif */
}
/* =========== Submenu =============*/
#topnav .navigation-menu > li .submenu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
border: 1px solid #e7e7e7;
padding: 15px 0;
list-style: none;
min-width: 200px;
visibility: hidden;
opacity: 0;
margin-top: 10px;
-webkit-transition: all .2s ease;
transition: all .2s ease;
background-color: #FEF0E1; /* Background submenu */
box-shadow: 0 0 24px 0 rgba(0, 0, 0, 0.06), 0 1px 0 0 rgba(0, 0, 0, 0.02);
text-align: left;
}
#topnav .navigation-menu > li:hover > .submenu {
visibility: visible;
opacity: 1;
margin-top: 0;
}
#topnav .navigation-menu .submenu li a {
display: block;
padding: 8px 25px;
clear: both;
font-weight: normal;
white-space: nowrap;
font-size: 15px; font-size: 15px;
color: #2F2F2F; margin-right: 8px;
-webkit-transition: all .35s ease; -webkit-transition: all .5s ease;
transition: all .35s ease; transition: all .5s ease;
} }
/* ATURAN HOVER YANG DIPERBAIKI */
#topnav .navigation-menu .submenu li a:hover, #topnav .navigation-menu > li > a:hover, #topnav .navigation-menu > li > a:focus {
#topnav .navigation-menu .submenu li a:focus, background-color: transparent;
#topnav .navigation-menu .submenu li a:active {
background-color: #FFA14F; /* Warna hover submenu */
color: #2F2F2F;
} }
/* /*
Responsive Menu Responsive Menu
*/ */
@@ -328,16 +296,33 @@ File: Main Css File
margin-right: 10px; margin-right: 10px;
} }
#topnav .navigation-menu > li:hover a { #topnav .navigation-menu > li:hover a {
color: #2F2F2F !important; color: #ffffff;
} }
#topnav .navigation-menu > li:hover a i { #topnav .navigation-menu > li:hover a i {
color: #2F2F2F !important; color: #ffffff;
} }
#topnav .navigation-menu > li > ul > li.has-submenu:active > a, #topnav .navigation-menu > li > ul > li.has-submenu:active > a,
#topnav .navigation-menu > li > ul > li.has-submenu:hover > a { #topnav .navigation-menu > li > ul > li.has-submenu:hover > a {
color: #2F2F2F !important; color: #02c0ce;
}
#topnav .navigation-menu > li .submenu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
border: 1px solid #e7e7e7;
padding: 15px 0;
list-style: none;
min-width: 200px;
text-align: left;
visibility: hidden;
opacity: 0;
margin-top: 10px;
-webkit-transition: all .2s ease;
transition: all .2s ease;
background-color: #ffffff;
box-shadow: 0 0 24px 0 rgba(0, 0, 0, 0.06), 0 1px 0 0 rgba(0, 0, 0, 0.02);
} }
#topnav .navigation-menu > li .submenu.megamenu { #topnav .navigation-menu > li .submenu.megamenu {
white-space: nowrap; white-space: nowrap;
width: auto; width: auto;
@@ -393,7 +378,7 @@ File: Main Css File
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 2px; letter-spacing: 2px;
font-weight: 500; font-weight: 500;
background-color: #FEF0E1; /* Background submenu */ color: #313a46;
} }
#topnav .navbar-toggle { #topnav .navbar-toggle {
display: none; display: none;
@@ -413,11 +398,10 @@ File: Main Css File
#topnav .navigation-menu { #topnav .navigation-menu {
float: none; float: none;
max-height: 400px; max-height: 400px;
text-align: center; text-align: left;
} }
#topnav .navigation-menu > li { #topnav .navigation-menu > li {
display: block; display: block;
text-align: center;
} }
#topnav .navigation-menu > li > a { #topnav .navigation-menu > li > a {
color: #313a46; color: #313a46;
@@ -446,8 +430,7 @@ File: Main Css File
color: #313a46; color: #313a46;
} }
#topnav .navigation-menu > li .submenu li a:hover { #topnav .navigation-menu > li .submenu li a:hover {
background-color: #FFA14F; color: #02c0ce;
color: #313a46;
} }
#topnav .navigation-menu > li .submenu li.has-submenu > a:after { #topnav .navigation-menu > li .submenu li.has-submenu > a:after {
content: "\F140"; content: "\F140";
@@ -502,10 +485,10 @@ File: Main Css File
overflow-y: auto; overflow-y: auto;
} }
#topnav .has-submenu.active a { #topnav .has-submenu.active a {
color: #FFA14F; color: #02c0ce;
} }
#topnav .has-submenu.active a:active, #topnav .has-submenu.active a:focus, #topnav .has-submenu.active a i { #topnav .has-submenu.active a:active, #topnav .has-submenu.active a:focus, #topnav .has-submenu.active a i {
color: #FFA14F; color: #02c0ce;
} }
} }
@@ -1131,11 +1114,11 @@ code {
} }
.bg-primary { .bg-primary {
background-color: #FFA14F !important; background-color: #2d7bf4 !important;
} }
.bg-success { .bg-success {
background-color: #77C470 !important; background-color: #0acf97 !important;
} }
.bg-info { .bg-info {
@@ -1143,11 +1126,11 @@ code {
} }
.bg-warning { .bg-warning {
background-color: #FAA645 !important; background-color: #f9bc0b !important;
} }
.bg-danger { .bg-danger {
background-color: #E8563C !important; background-color: #f1556c !important;
} }
.bg-muted { .bg-muted {
@@ -1196,7 +1179,7 @@ code {
} }
.text-warning { .text-warning {
color: #FAA645 !important; color: #f9bc0b !important;
} }
.text-success { .text-success {
@@ -1233,7 +1216,7 @@ code {
} }
.border-warning { .border-warning {
border-color: #FAA645 !important; border-color: #f9bc0b !important;
} }
.border-danger { .border-danger {
@@ -1265,12 +1248,12 @@ code {
} }
.badge-custom { .badge-custom {
background: #FFA14F; background: #02c0ce;
color: #ffffff; color: #ffffff;
} }
.badge-primary { .badge-primary {
background-color: #FFA14F; background-color: #2d7bf4;
} }
.badge-secondary { .badge-secondary {
@@ -1286,7 +1269,7 @@ code {
} }
.badge-warning { .badge-warning {
background-color: #FAA645; background-color: #f9bc0b;
color: #ffffff; color: #ffffff;
} }
@@ -1679,15 +1662,15 @@ code {
} }
.btn-warning { .btn-warning {
background-color: #FAA645; background-color: #f9bc0b;
border: 1px solid #FAA645; border: 1px solid #f9bc0b;
} }
.btn-warning:hover, .btn-warning:focus, .btn-warning:active, .btn-warning.active, .btn-warning.focus, .btn-warning:active, .btn-warning:hover, .btn-warning:focus, .btn-warning:active, .btn-warning.active, .btn-warning.focus, .btn-warning:active,
.btn-warning:focus, .btn-warning:hover, .open > .dropdown-toggle.btn-warning, .btn-warning:not([disabled]):not(.disabled).active, .btn-warning:focus, .btn-warning:hover, .open > .dropdown-toggle.btn-warning, .btn-warning:not([disabled]):not(.disabled).active,
.btn-warning:not([disabled]):not(.disabled):active, .show > .btn-warning.dropdown-toggle { .btn-warning:not([disabled]):not(.disabled):active, .show > .btn-warning.dropdown-toggle {
background-color: #FAA645; background-color: #e5ac06;
border-color: #FAA645; border-color: #e5ac06;
} }
.btn-warning.focus, .btn-warning:focus, .btn-outline-warning.focus, .btn-outline-warning:focus, .btn-warning.focus, .btn-warning:focus, .btn-outline-warning.focus, .btn-outline-warning:focus,
@@ -1802,13 +1785,13 @@ code {
} }
.btn-outline-warning { .btn-outline-warning {
color: #FAA645; color: #f9bc0b;
border-color: #FAA645; border-color: #f9bc0b;
} }
.btn-outline-warning:hover { .btn-outline-warning:hover {
background-color: #FAA645; background-color: #f9bc0b;
border-color: #FAA645; border-color: #f9bc0b;
} }
.btn-outline-danger { .btn-outline-danger {
@@ -2000,7 +1983,7 @@ code {
} }
.ribbon-box .ribbon-warning { .ribbon-box .ribbon-warning {
background: #FAA645; background: #f9bc0b;
} }
.ribbon-box .ribbon-warning:before { .ribbon-box .ribbon-warning:before {
@@ -2143,7 +2126,7 @@ code {
} }
.ribbon-box .ribbon-two-warning span { .ribbon-box .ribbon-two-warning span {
background: #FAA645; background: #f9bc0b;
} }
.ribbon-box .ribbon-two-warning span:before { .ribbon-box .ribbon-two-warning span:before {
@@ -2426,8 +2409,8 @@ code {
} }
.checkbox-warning input[type="checkbox"]:checked + label::before { .checkbox-warning input[type="checkbox"]:checked + label::before {
background-color: #FAA645; background-color: #f9bc0b;
border-color: #FAA645; border-color: #f9bc0b;
} }
.checkbox-warning input[type="checkbox"]:checked + label::after { .checkbox-warning input[type="checkbox"]:checked + label::after {
@@ -2601,15 +2584,15 @@ code {
} }
.radio-warning input[type="radio"] + label::after { .radio-warning input[type="radio"] + label::after {
background-color: #FAA645; background-color: #f9bc0b;
} }
.radio-warning input[type="radio"]:checked + label::before { .radio-warning input[type="radio"]:checked + label::before {
border-color: #FAA645; border-color: #f9bc0b;
} }
.radio-warning input[type="radio"]:checked + label::after { .radio-warning input[type="radio"]:checked + label::after {
background-color: #FAA645; background-color: #f9bc0b;
} }
.radio-success input[type="radio"] + label::after { .radio-success input[type="radio"] + label::after {
@@ -2682,7 +2665,7 @@ code {
} }
.alert-warning { .alert-warning {
color: #FAA645; color: #f9bc0b;
background-color: #fffaeb; background-color: #fffaeb;
border-color: #fbd76f; border-color: #fbd76f;
} }
@@ -2741,9 +2724,9 @@ code {
} }
.jq-icon-warning { .jq-icon-warning {
background-color: #FAA645; background-color: #f9bc0b;
color: #ffffff; color: #ffffff;
border-color: #FAA645; border-color: #f9bc0b;
} }
.jq-icon-error { .jq-icon-error {
@@ -3146,7 +3129,7 @@ code {
} }
.progress-bar-warning { .progress-bar-warning {
background-color: #FAA645; background-color: #f9bc0b;
} }
.progress-bar-danger { .progress-bar-danger {
@@ -3280,8 +3263,8 @@ code {
} }
.swal2-icon.swal2-warning { .swal2-icon.swal2-warning {
color: #FAA645; color: #f9bc0b;
border-color: #FAA645; border-color: #f9bc0b;
} }
.swal2-icon.swal2-error { .swal2-icon.swal2-error {
@@ -3397,7 +3380,7 @@ code {
} }
.ct-chart .ct-series.ct-series-g .ct-bar, .ct-chart .ct-series.ct-series-g .ct-line, .ct-chart .ct-series.ct-series-g .ct-point, .ct-chart .ct-series.ct-series-g .ct-slice-donut { .ct-chart .ct-series.ct-series-g .ct-bar, .ct-chart .ct-series.ct-series-g .ct-line, .ct-chart .ct-series.ct-series-g .ct-point, .ct-chart .ct-series.ct-series-g .ct-slice-donut {
stroke: #FAA645; stroke: #f9bc0b;
} }
.ct-series-a .ct-area, .ct-series-a .ct-slice-pie { .ct-series-a .ct-area, .ct-series-a .ct-slice-pie {
@@ -3590,7 +3573,7 @@ code {
} }
.message-list li.orange-dot .col-mail-1 .dot { .message-list li.orange-dot .col-mail-1 .dot {
border-color: #FAA645; border-color: #f9bc0b;
} }
.message-list li.green-dot .col-mail-1 .dot { .message-list li.green-dot .col-mail-1 .dot {
View File
@@ -107,31 +107,60 @@ class _CovidExpertiseWizardState extends State<CovidExpertiseWizard> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_sectionTitle('PCR COVID'), for (var i = 0; i < _covidVirusFields.length; i += 1)
for (final virus in _covidVirusFields) CollapsibleExpertiseSection(
Card( index: i + 1,
margin: const EdgeInsets.only(bottom: 12), title: _covidVirusFields[i].label,
shape: RoundedRectangleBorder( completed: _hasAny([
borderRadius: BorderRadius.circular(8), _covidVirusFields[i].resultField,
), _covidVirusFields[i].unitField,
child: Padding( _covidVirusFields[i].referenceField,
padding: const EdgeInsets.all(12), ]),
child: Column( initiallyExpanded:
crossAxisAlignment: CrossAxisAlignment.start, i == 0 &&
children: [ !_hasAny([
_sectionTitle(virus.label, compact: true), _covidVirusFields[i].resultField,
_select(virus.resultField, virus.label, _covidResultOptions), _covidVirusFields[i].unitField,
_text(virus.unitField, 'Satuan'), ]),
_text(virus.referenceField, 'Nilai Rujukan'), children: [
], _select(
_covidVirusFields[i].resultField,
_covidVirusFields[i].label,
_covidResultOptions,
), ),
), _text(_covidVirusFields[i].unitField, 'Satuan'),
_text(_covidVirusFields[i].referenceField, 'Nilai Rujukan'),
],
), ),
_text('covid_interpretasi', 'Interpretasi', minLines: 6, maxLines: 9), CollapsibleExpertiseSection(
index: _covidVirusFields.length + 1,
title: 'Interpretasi',
completed: _hasAny(['covid_interpretasi']),
children: [
_text(
'covid_interpretasi',
'Interpretasi',
minLines: 6,
maxLines: 9,
),
],
),
], ],
); );
} }
bool _hasAny(List<String> names) {
for (final name in names) {
if ((widget.selectValues[name] ?? '').trim().isNotEmpty) {
return true;
}
if ((widget.textControllers[name]?.text ?? '').trim().isNotEmpty) {
return true;
}
}
return false;
}
Widget _antibioticSensitivity() { Widget _antibioticSensitivity() {
return AntibioticSensitivityPanel( return AntibioticSensitivityPanel(
textControllers: widget.textControllers, textControllers: widget.textControllers,
+169 -6
View File
@@ -27,7 +27,7 @@ class ExpertiseWizardShell extends StatelessWidget {
slivers: [ slivers: [
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 10), padding: const EdgeInsets.fromLTRB(18, 18, 18, 10),
child: _WizardHeader( child: _WizardHeader(
currentStep: safeStep, currentStep: safeStep,
steps: steps, steps: steps,
@@ -40,14 +40,17 @@ class ExpertiseWizardShell extends StatelessWidget {
duration: const Duration(milliseconds: 180), duration: const Duration(milliseconds: 180),
child: Padding( child: Padding(
key: ValueKey<int>(safeStep), key: ValueKey<int>(safeStep),
padding: const EdgeInsets.fromLTRB(16, 0, 16, 18), padding: const EdgeInsets.fromLTRB(18, 0, 18, 18),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Text( Text(
title, title,
style: Theme.of(context).textTheme.titleLarge style: Theme.of(context).textTheme.titleLarge
?.copyWith(fontWeight: FontWeight.w800), ?.copyWith(
fontWeight: FontWeight.w900,
color: const Color(0xFF080D3D),
),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Align( Align(
@@ -99,7 +102,7 @@ class _WizardHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
height: 46, height: 50,
child: ListView.separated( child: ListView.separated(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: steps.length, itemCount: steps.length,
@@ -113,6 +116,19 @@ class _WizardHeader extends StatelessWidget {
onSelected: (_) => onStepChanged(index), onSelected: (_) => onStepChanged(index),
showCheckmark: false, showCheckmark: false,
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
selectedColor: const Color(0xFFE0F2F1),
backgroundColor: Colors.white,
side: BorderSide(
color: selected
? const Color(0xFF0F766E)
: const Color(0xFFE1E8F5),
),
labelStyle: TextStyle(
color: selected
? const Color(0xFF0F766E)
: const Color(0xFF47517A),
fontWeight: FontWeight.w800,
),
); );
}, },
), ),
@@ -161,7 +177,7 @@ class _WizardBottomToolbar extends StatelessWidget {
children: [ children: [
OutlinedButton.icon( OutlinedButton.icon(
onPressed: canPrevious ? onPrevious : null, onPressed: canPrevious ? onPrevious : null,
icon: const Icon(Icons.chevron_left), icon: const Icon(Icons.chevron_left_rounded),
label: const Text('Previous'), label: const Text('Previous'),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
@@ -178,7 +194,7 @@ class _WizardBottomToolbar extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
FilledButton.icon( FilledButton.icon(
onPressed: canNext ? onNext : null, onPressed: canNext ? onNext : null,
icon: const Icon(Icons.chevron_right), icon: const Icon(Icons.chevron_right_rounded),
label: const Text('Next'), label: const Text('Next'),
), ),
], ],
@@ -188,6 +204,153 @@ class _WizardBottomToolbar extends StatelessWidget {
} }
} }
class CollapsibleExpertiseSection extends StatefulWidget {
const CollapsibleExpertiseSection({
super.key,
required this.index,
required this.title,
required this.children,
this.completed = false,
this.initiallyExpanded = false,
this.color,
});
final int index;
final String title;
final List<Widget> children;
final bool completed;
final bool initiallyExpanded;
final Color? color;
@override
State<CollapsibleExpertiseSection> createState() =>
_CollapsibleExpertiseSectionState();
}
class _CollapsibleExpertiseSectionState
extends State<CollapsibleExpertiseSection> {
late bool _expanded;
@override
void initState() {
super.initState();
_expanded = widget.initiallyExpanded;
}
@override
Widget build(BuildContext context) {
final color = widget.color ?? _sectionColor(widget.index);
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: const Color(0xFFE1E8F5)),
borderRadius: BorderRadius.circular(14),
boxShadow: [
BoxShadow(
color: const Color(0xFF2563EB).withValues(alpha: 0.04),
blurRadius: 14,
offset: const Offset(0, 8),
),
],
),
child: Column(
children: [
InkWell(
borderRadius: BorderRadius.circular(14),
onTap: () => setState(() => _expanded = !_expanded),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 44,
height: 44,
alignment: Alignment.center,
decoration: BoxDecoration(
color: color.withValues(alpha: 0.11),
shape: BoxShape.circle,
),
child: Text(
'${widget.index}.',
style: TextStyle(
color: color,
fontSize: 20,
fontWeight: FontWeight.w900,
),
),
),
const SizedBox(width: 14),
Expanded(
child: Text(
widget.title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: const Color(0xFF080D3D),
fontWeight: FontWeight.w900,
),
),
),
if (widget.completed) ...[
Container(
width: 34,
height: 34,
decoration: const BoxDecoration(
color: Color(0xFF0F766E),
shape: BoxShape.circle,
),
child: const Icon(
Icons.check_rounded,
color: Colors.white,
size: 22,
),
),
const SizedBox(width: 10),
],
Icon(
_expanded
? Icons.keyboard_arrow_up_rounded
: Icons.keyboard_arrow_down_rounded,
color: const Color(0xFF0F172A),
size: 30,
),
],
),
),
),
AnimatedCrossFade(
firstChild: const SizedBox.shrink(),
secondChild: Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: widget.children,
),
),
crossFadeState: _expanded
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 180),
),
],
),
);
}
Color _sectionColor(int index) {
const colors = [
Color(0xFF0F766E),
Color(0xFF0B7BFF),
Color(0xFF7C3AED),
Color(0xFFFF6B00),
Color(0xFF0891B2),
Color(0xFFDB2777),
];
return colors[(index - 1).abs() % colors.length];
}
}
class CompactMultiSelectField extends StatelessWidget { class CompactMultiSelectField extends StatelessWidget {
const CompactMultiSelectField({ const CompactMultiSelectField({
super.key, super.key,
+152 -47
View File
@@ -189,16 +189,6 @@ class _ExpertiseScreenState extends State<ExpertiseScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( 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>>( body: FutureBuilder<Map<String, dynamic>>(
future: _future, future: _future,
builder: (context, snapshot) { builder: (context, snapshot) {
@@ -217,41 +207,9 @@ class _ExpertiseScreenState extends State<ExpertiseScreen> {
_prepareFields(data); _prepareFields(data);
final isSupervisor = _isSupervisor(user['previlage']?.toString()); final isSupervisor = _isSupervisor(user['previlage']?.toString());
final summaryCard = Card( final summaryCard = _ExpertiseSummaryCard(
shape: RoundedRectangleBorder( item: item,
borderRadius: BorderRadius.circular(8), dlp: _currentDlp,
),
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 final expertiseContent = _currentDlp.isEmpty
@@ -426,8 +384,18 @@ class _ExpertiseScreenState extends State<ExpertiseScreen> {
if (_currentDlp.isEmpty) { if (_currentDlp.isEmpty) {
return ListView( return ListView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.fromLTRB(18, 18, 18, 24),
children: [ children: [
SafeArea(
bottom: false,
child: _ExpertiseHeader(
title: 'Expertise',
subtitle: 'Pilih Template',
saving: _saving,
onRefresh: _reload,
),
),
const SizedBox(height: 22),
summaryCard, summaryCard,
const SizedBox(height: 14), const SizedBox(height: 14),
expertiseContent, expertiseContent,
@@ -436,9 +404,19 @@ class _ExpertiseScreenState extends State<ExpertiseScreen> {
} }
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), padding: const EdgeInsets.fromLTRB(18, 18, 18, 0),
child: Column( child: Column(
children: [ children: [
SafeArea(
bottom: false,
child: _ExpertiseHeader(
title: 'Expertise',
subtitle: _currentDlp,
saving: _saving,
onRefresh: _reload,
),
),
const SizedBox(height: 22),
summaryCard, summaryCard,
const SizedBox(height: 14), const SizedBox(height: 14),
Expanded(child: expertiseContent), Expanded(child: expertiseContent),
@@ -450,3 +428,130 @@ class _ExpertiseScreenState extends State<ExpertiseScreen> {
); );
} }
} }
class _ExpertiseHeader extends StatelessWidget {
const _ExpertiseHeader({
required this.title,
required this.subtitle,
required this.saving,
required this.onRefresh,
});
final String title;
final String subtitle;
final bool saving;
final VoidCallback onRefresh;
@override
Widget build(BuildContext context) {
return Row(
children: [
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.arrow_back_ios_new_rounded),
color: const Color(0xFF080D3D),
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
color: const Color(0xFF080D3D),
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 4),
Text(
subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: const Color(0xFF47517A),
fontWeight: FontWeight.w700,
),
),
],
),
),
IconButton(
onPressed: saving ? null : onRefresh,
icon: const Icon(Icons.refresh_rounded),
color: const Color(0xFF063B60),
tooltip: 'Refresh',
),
],
);
}
}
class _ExpertiseSummaryCard extends StatelessWidget {
const _ExpertiseSummaryCard({required this.item, required this.dlp});
final Map<String, dynamic> item;
final String dlp;
@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: [
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(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),
),
StatusPill(status: item['status']?.toString() ?? 'NEW'),
if (dlp.isNotEmpty)
BadgeLabel(text: dlp, color: const Color(0xFF0F766E)),
],
),
const SizedBox(height: 12),
Text(
item['reques']?.toString() ?? '-',
style: const TextStyle(
color: Color(0xFF47517A),
fontWeight: FontWeight.w800,
height: 1.35,
),
),
],
),
);
}
}
@@ -116,64 +116,137 @@ class _KulturExpertiseWizardState extends State<KulturExpertiseWizard> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_sectionTitle('A. Sediaan Langsung'), CollapsibleExpertiseSection(
_sectionTitle('1. Pewarna Gram', compact: true), index: 1,
_select('id_selepitel', 'Sel Epitel', _options('jsonselepitel')), title: 'Pewarnaan Gram',
_select('id_selradang', 'Sel Radang', _options('jsonselradang')), completed: _hasAny([
_select( 'id_selepitel',
'id_mikroorganisme', 'id_selradang',
'Mikroorganisme', 'id_mikroorganisme',
_options('jsonmikroorganisme'), 'id_mikroorganismeoptional',
), ]),
_multiSelect( initiallyExpanded: !_hasAny(['id_selepitel', 'id_selradang']),
'id_mikroorganismeoptional', children: [
'Bisa memilih lebih dari satu', _select('id_selepitel', 'Sel Epitel', _options('jsonselepitel')),
_options('jsonmikroorganismeoptional'), _select('id_selradang', 'Sel Radang', _options('jsonselradang')),
_select(
'id_mikroorganisme',
'Mikroorganisme',
_options('jsonmikroorganisme'),
),
_multiSelect(
'id_mikroorganismeoptional',
'Bisa memilih lebih dari satu',
_options('jsonmikroorganismeoptional'),
),
],
), ),
_nugentScore(), _nugentScore(),
_sectionTitle('2. Pewarnaan Ziehl Neelsen', compact: true), CollapsibleExpertiseSection(
_select( index: 2,
'id_pewarnaanziehlnielsen', title: 'Pewarnaan Ziehl Neelsen',
'Pagi', completed: _hasAny([
_options('jsonpewarnaanziehlnielsen'), 'id_pewarnaanziehlnielsen',
'id_pewarnaanziehlnielsensewaktu',
]),
children: [
_select(
'id_pewarnaanziehlnielsen',
'Pagi',
_options('jsonpewarnaanziehlnielsen'),
),
_select(
'id_pewarnaanziehlnielsensewaktu',
'Sewaktu',
_options('jsonpewarnaanziehlnielsen'),
),
],
), ),
_select( CollapsibleExpertiseSection(
'id_pewarnaanziehlnielsensewaktu', index: 3,
'Sewaktu', title: 'Pewarnaan KOH',
_options('jsonpewarnaanziehlnielsen'), completed: _hasAny(['id_pewarnaankoh', 'id_pewarnaankohoptional']),
children: [
_select(
'id_pewarnaankoh',
'Pewarnaan KOH',
_options('jsonpewarnaankoh', _kohFallback),
),
_text('id_pewarnaankohoptional', 'Pewarnaan KOH Manual'),
],
), ),
_select( CollapsibleExpertiseSection(
'id_pewarnaankoh', index: 4,
'3. Pewarnaan KOH', title: 'Pewarnaan Neisser',
_options('jsonpewarnaankoh', _kohFallback), completed: _hasAny(['id_pewarnaanneisser']),
children: [
_select(
'id_pewarnaanneisser',
'Pewarnaan Neisser',
_options('jsonpewarnaanneisser'),
),
],
), ),
_text('id_pewarnaankohoptional', 'Pewarnaan KOH Manual'), CollapsibleExpertiseSection(
_select( index: 5,
'id_pewarnaanneisser', title: 'Pewarnaan Negatif',
'4. Pewarnaan Neisser', completed: _hasAny(['id_pewarnaannegatif']),
_options('jsonpewarnaanneisser'), children: [
_select(
'id_pewarnaannegatif',
'Pewarnaan Negatif',
_options('jsonpewarnaannegatif'),
),
],
), ),
_select( CollapsibleExpertiseSection(
'id_pewarnaannegatif', index: 6,
'5. Pewarnaan Negatif', title: 'Pewarnaan Spora',
_options('jsonpewarnaannegatif'), completed: _hasAny(['id_pewarnaanspora']),
children: [
_select(
'id_pewarnaanspora',
'Pewarnaan Spora',
_options('jsonpewarnaanspora'),
),
],
), ),
_select( CollapsibleExpertiseSection(
'id_pewarnaanspora', index: 7,
'6. Pewarnaan Spora', title: 'Pewarnaan Giemsa',
_options('jsonpewarnaanspora'), completed: _hasAny([
'id_pewarnaangiesma',
'id_pewarnaangiesmaoptional',
]),
children: [
_select(
'id_pewarnaangiesma',
'Pewarnaan Giemsa',
_options('jsonpewarnaangiemsa', _giemsaFallback),
),
_text('id_pewarnaangiesmaoptional', 'Pewarnaan Giemsa Manual'),
],
), ),
_select(
'id_pewarnaangiesma',
'7. Pewarnaan Giemsa',
_options('jsonpewarnaangiemsa', _giemsaFallback),
),
_text('id_pewarnaangiesmaoptional', 'Pewarnaan Giemsa Manual'),
_saveDirectSmearButton(), _saveDirectSmearButton(),
], ],
); );
} }
bool _hasAny(List<String> names) {
for (final name in names) {
if ((widget.selectValues[name] ?? '').trim().isNotEmpty) {
return true;
}
if ((widget.textControllers[name]?.text ?? '').trim().isNotEmpty) {
return true;
}
if ((widget.multiValues[name] ?? const <String>{}).isNotEmpty) {
return true;
}
}
return false;
}
Widget _saveDirectSmearButton() { Widget _saveDirectSmearButton() {
return Padding( return Padding(
padding: const EdgeInsets.only(top: 4, bottom: 12), padding: const EdgeInsets.only(top: 4, bottom: 12),
@@ -108,20 +108,58 @@ class _LeptospiraExpertiseWizardState extends State<LeptospiraExpertiseWizard> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_sectionTitle('IgM IgG Leptospira'), CollapsibleExpertiseSection(
_select('igg_parameter', 'IgG Leptospira', const [ index: 1,
'Positif', title: 'IgG Leptospira',
'Negatif', completed: _hasAny(['igg_parameter']),
]), initiallyExpanded: !_hasAny(['igg_parameter', 'igm_parameter']),
_select('igm_parameter', 'IgM Leptospira', const [ children: [
'Positif', _select('igg_parameter', 'IgG Leptospira', const [
'Negatif', 'Positif',
]), 'Negatif',
_text('iggigm_interpretasi', 'Interpretasi', minLines: 6, maxLines: 9), ]),
],
),
CollapsibleExpertiseSection(
index: 2,
title: 'IgM Leptospira',
completed: _hasAny(['igm_parameter']),
children: [
_select('igm_parameter', 'IgM Leptospira', const [
'Positif',
'Negatif',
]),
],
),
CollapsibleExpertiseSection(
index: 3,
title: 'Interpretasi',
completed: _hasAny(['iggigm_interpretasi']),
children: [
_text(
'iggigm_interpretasi',
'Interpretasi',
minLines: 6,
maxLines: 9,
),
],
),
], ],
); );
} }
bool _hasAny(List<String> names) {
for (final name in names) {
if ((widget.selectValues[name] ?? '').trim().isNotEmpty) {
return true;
}
if ((widget.textControllers[name]?.text ?? '').trim().isNotEmpty) {
return true;
}
}
return false;
}
Widget _antibioticSensitivity() { Widget _antibioticSensitivity() {
return AntibioticSensitivityPanel( return AntibioticSensitivityPanel(
textControllers: widget.textControllers, textControllers: widget.textControllers,
@@ -113,63 +113,128 @@ class _PewarnaanLangsungExpertiseWizardState
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_sectionTitle('A. Sediaan Langsung'), CollapsibleExpertiseSection(
_sectionTitle('1. Pewarnaan Gram', compact: true), index: 1,
_select('lsg_selepitel', 'Sel Epitel', _options('jsonselepitel')), title: 'Pewarnaan Gram',
_select('lsg_selradang', 'Sel Radang', _options('jsonselradang')), completed: _hasAny([
_select( 'lsg_selepitel',
'lsg_mikroorganisme', 'lsg_selradang',
'Mikroorganisme', 'lsg_mikroorganisme',
_options('jsonmikroorganisme'), 'lsg_mikroorganismeoptional',
]),
initiallyExpanded: !_hasAny(['lsg_selepitel', 'lsg_selradang']),
children: [
_select('lsg_selepitel', 'Sel Epitel', _options('jsonselepitel')),
_select('lsg_selradang', 'Sel Radang', _options('jsonselradang')),
_select(
'lsg_mikroorganisme',
'Mikroorganisme',
_options('jsonmikroorganisme'),
),
_multiSelect(
'lsg_mikroorganismeoptional',
'Bisa memilih lebih dari satu',
_options('jsonmikroorganismeoptional'),
),
],
), ),
_multiSelect( CollapsibleExpertiseSection(
'lsg_mikroorganismeoptional', index: 2,
'Bisa memilih lebih dari satu', title: 'Pewarnaan Ziehl Neelsen',
_options('jsonmikroorganismeoptional'), completed: _hasAny([
'lsg_pewarnaanziehlnielsen',
'lsg_pewarnaanziehlnielsensewaktu',
]),
children: [
_select(
'lsg_pewarnaanziehlnielsen',
'Pagi',
_options('jsonpewarnaanziehlnielsen'),
),
_select(
'lsg_pewarnaanziehlnielsensewaktu',
'Sewaktu',
_options('jsonpewarnaanziehlnielsen'),
),
],
), ),
_sectionTitle('2. Pewarnaan Ziehl Neelsen', compact: true), CollapsibleExpertiseSection(
_select( index: 3,
'lsg_pewarnaanziehlnielsen', title: 'Pewarnaan KOH',
'Pagi', completed: _hasAny(['lsg_pewarnaankoh', 'lsg_pewarnaankohoptional']),
_options('jsonpewarnaanziehlnielsen'), children: [
_select(
'lsg_pewarnaankoh',
'Pewarnaan KOH',
_options('jsonpewarnaankoh', _kohFallback),
),
_text('lsg_pewarnaankohoptional', 'Pewarnaan KOH Manual'),
],
), ),
_select( CollapsibleExpertiseSection(
'lsg_pewarnaanziehlnielsensewaktu', index: 4,
'Sewaktu', title: 'Pewarnaan Neisser',
_options('jsonpewarnaanziehlnielsen'), completed: _hasAny(['lsg_pewarnaanneisser']),
children: [
_select(
'lsg_pewarnaanneisser',
'Pewarnaan Neisser',
_options('jsonpewarnaanneisser'),
),
],
), ),
_select( CollapsibleExpertiseSection(
'lsg_pewarnaankoh', index: 5,
'3. Pewarnaan KOH', title: 'Pewarnaan Negatif',
_options('jsonpewarnaankoh', _kohFallback), completed: _hasAny(['lsg_pewarnaannegatif']),
children: [
_select(
'lsg_pewarnaannegatif',
'Pewarnaan Negatif',
_options('jsonpewarnaannegatif'),
),
],
), ),
_text('lsg_pewarnaankohoptional', 'Pewarnaan KOH Manual'), CollapsibleExpertiseSection(
_select( index: 6,
'lsg_pewarnaanneisser', title: 'Pewarnaan Spora',
'4. Pewarnaan Neisser', completed: _hasAny(['lsg_pewarnaanspora']),
_options('jsonpewarnaanneisser'), children: [
_select(
'lsg_pewarnaanspora',
'Pewarnaan Spora',
_options('jsonpewarnaanspora'),
),
],
), ),
_select( CollapsibleExpertiseSection(
'lsg_pewarnaannegatif', index: 7,
'5. Pewarnaan Negatif', title: 'Pewarnaan Giemsa',
_options('jsonpewarnaannegatif'), completed: _hasAny([
'lsg_pewarnaangiesma',
'lsg_pewarnaangiesmaoptional',
]),
children: [
_select(
'lsg_pewarnaangiesma',
'Pewarnaan Giemsa',
_options('jsonpewarnaangiemsa', _giemsaFallback),
),
_text('lsg_pewarnaangiesmaoptional', 'Pewarnaan Giemsa Manual'),
],
), ),
_select( CollapsibleExpertiseSection(
'lsg_pewarnaangiesma', index: 8,
'6. Pewarnaan Giemsa', title: 'Pewarnaan Lain-Lain',
_options('jsonpewarnaangiemsa', _giemsaFallback), completed: _hasAny(['lsg_pewarnaanlain']),
), children: [
_text('lsg_pewarnaangiesmaoptional', 'Pewarnaan Giemsa Manual'), _text(
_select( 'lsg_pewarnaanlain',
'lsg_pewarnaanspora', 'Pewarnaan Lain-Lain',
'7. Pewarnaan Spora', minLines: 5,
_options('jsonpewarnaanspora'), maxLines: 8,
), ),
_text( ],
'lsg_pewarnaanlain',
'8. Pewarnaan Lain-Lain',
minLines: 5,
maxLines: 8,
), ),
_nugentScore(), _nugentScore(),
_saveDirectSmearButton(), _saveDirectSmearButton(),
@@ -177,6 +242,21 @@ class _PewarnaanLangsungExpertiseWizardState
); );
} }
bool _hasAny(List<String> names) {
for (final name in names) {
if ((widget.selectValues[name] ?? '').trim().isNotEmpty) {
return true;
}
if ((widget.textControllers[name]?.text ?? '').trim().isNotEmpty) {
return true;
}
if ((widget.multiValues[name] ?? const <String>{}).isNotEmpty) {
return true;
}
}
return false;
}
Widget _saveDirectSmearButton() { Widget _saveDirectSmearButton() {
return Padding( return Padding(
padding: const EdgeInsets.only(top: 4, bottom: 12), padding: const EdgeInsets.only(top: 4, bottom: 12),
@@ -119,71 +119,135 @@ class _TbcExpertiseWizardState extends State<TbcExpertiseWizard> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_sectionTitle('Jenis Terduga / Pasien TBC'), CollapsibleExpertiseSection(
_multiSelect('jenis_pasien_tbc', 'Jenis Terduga / Pasien TBC', const [ index: 1,
'TBC SO', title: 'Jenis Terduga / Pasien TBC',
'TBC RO', completed: _hasAny([
'Anak', 'jenis_pasien_tbc',
'HIV', 'id_noidsediaan',
'DM', 'id_tanggalpengambilancontohuji',
]), 'id_tanggalpengirimancontohuji',
_text('id_noidsediaan', 'No. Identitas Sediaan'), 'id_lamariwayatpengobatantb',
_text( ]),
'id_tanggalpengambilancontohuji', initiallyExpanded: !_hasAny(['jenis_pasien_tbc', 'id_noidsediaan']),
'Tanggal Pengambilan Contoh Uji', children: [
), _multiSelect(
_text('id_tanggalpengirimancontohuji', 'Tanggal Pengiriman Contoh Uji'), 'jenis_pasien_tbc',
_select( 'Jenis Terduga / Pasien TBC',
'id_lamariwayatpengobatantb', const ['TBC SO', 'TBC RO', 'Anak', 'HIV', 'DM'],
'Lama Riwayat Pengobatan Terduga/Pasien TBC', ),
const [ _text('id_noidsediaan', 'No. Identitas Sediaan'),
'Riwayat Pengobatan >= 5 tahun', _text(
'Riwayat Pengobatan < 5 tahun', 'id_tanggalpengambilancontohuji',
'Tanggal Pengambilan Contoh Uji',
),
_text(
'id_tanggalpengirimancontohuji',
'Tanggal Pengiriman Contoh Uji',
),
_select(
'id_lamariwayatpengobatantb',
'Lama Riwayat Pengobatan Terduga/Pasien TBC',
const [
'Riwayat Pengobatan >= 5 tahun',
'Riwayat Pengobatan < 5 tahun',
],
),
], ],
), ),
_select('id_lokasianatomi', 'Lokasi Anatomi', const [ CollapsibleExpertiseSection(
'Paru', index: 2,
'Ekstraparu', title: 'Identitas Nasional',
]), completed: _hasAny([
_text('id_ekstraparu', 'Ekstraparu'), 'id_lokasianatomi',
_select('id_alasanpemeriksaan', 'Alasan Pemeriksaan', const [ 'id_alasanpemeriksaan',
'Diagnosis TBC', 'id_noregfasyankes',
'Diagnosis Baseline TBC', 'id_noregkota',
'Akhir Pengobatan', ]),
]), children: [
_text( _select('id_lokasianatomi', 'Lokasi Anatomi', const [
'id_bulankemajuantbfollow', 'Paru',
'Pemantauan kemajuan pengobatan (Bulan ke)', 'Ekstraparu',
]),
_text('id_ekstraparu', 'Ekstraparu'),
_select('id_alasanpemeriksaan', 'Alasan Pemeriksaan', const [
'Diagnosis TBC',
'Diagnosis Baseline TBC',
'Akhir Pengobatan',
]),
_text(
'id_bulankemajuantbfollow',
'Pemantauan kemajuan pengobatan (Bulan ke)',
),
_text('id_bulanpemeriksaanulang', 'Pemeriksaan ulang (Bulan ke)'),
_text(
'id_bulanpemeriksaansetelahselesai',
'Pemeriksaan setelah selesai pengobatan (Bulan ke)',
),
_text('id_noregfasyankes', 'No.Reg.TBC/TBC RO Fasyankes'),
_text('id_noregkota', 'No.Reg.TBC/TBC RO Kab/Kota'),
],
), ),
_text('id_bulanpemeriksaanulang', 'Pemeriksaan ulang (Bulan ke)'), CollapsibleExpertiseSection(
_text( index: 3,
'id_bulanpemeriksaansetelahselesai', title: 'Contoh Uji dan Visual Dahak',
'Pemeriksaan setelah selesai pengobatan (Bulan ke)', completed: _hasAny([
'id_contohuji',
'id_contohujilainnya',
'visual_dahak_sewaktu',
'visual_dahak_pagi',
]),
children: [
_select('id_contohuji', 'Contoh Uji', const ['Dahak', 'Lainnya']),
_text('id_contohujilainnya', 'Contoh Uji Lainnya'),
_multiSelect('visual_dahak_sewaktu', 'Visual dahak Sewaktu', const [
'Nanah Lendir',
'Bercak darah',
'Air liur',
]),
_multiSelect('visual_dahak_pagi', 'Visual dahak Pagi', const [
'Nanah Lendir',
'Bercak darah',
'Air liur',
]),
],
), ),
_text('id_noregfasyankes', 'No.Reg.TBC/TBC RO Fasyankes'),
_text('id_noregkota', 'No.Reg.TBC/TBC RO Kab/Kota'),
_select('id_contohuji', 'Contoh Uji', const ['Dahak', 'Lainnya']),
_text('id_contohujilainnya', 'Contoh Uji Lainnya'),
_multiSelect('visual_dahak_sewaktu', 'Visual dahak Sewaktu', const [
'Nanah Lendir',
'Bercak darah',
'Air liur',
]),
_multiSelect('visual_dahak_pagi', 'Visual dahak Pagi', const [
'Nanah Lendir',
'Bercak darah',
'Air liur',
]),
], ],
); );
} }
bool _hasAny(List<String> names) {
for (final name in names) {
if ((widget.selectValues[name] ?? '').trim().isNotEmpty) {
return true;
}
if ((widget.textControllers[name]?.text ?? '').trim().isNotEmpty) {
return true;
}
if ((widget.multiValues[name] ?? const <String>{}).isNotEmpty) {
return true;
}
}
return false;
}
Widget _testType() { Widget _testType() {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_sectionTitle('Jenis Pemeriksaan'), CollapsibleExpertiseSection(
_select('id_jenispemeriksaantb', 'Jenis Pemeriksaan', _tbcTestTypes), index: 1,
title: 'Jenis Pemeriksaan',
completed: _hasAny(['id_jenispemeriksaantb']),
initiallyExpanded: !_hasAny(['id_jenispemeriksaantb']),
children: [
_select(
'id_jenispemeriksaantb',
'Jenis Pemeriksaan',
_tbcTestTypes,
),
],
),
], ],
); );
} }
@@ -110,31 +110,78 @@ class _ViralLoadExpertiseWizardState extends State<ViralLoadExpertiseWizard> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_sectionTitle('Parameter Viral Load'), CollapsibleExpertiseSection(
_select( index: 1,
'vl_parameter01', title: 'Parameter Viral Load',
'Parameter', completed: _hasAny([
_options('jsonvlparameter', const ['HIV RNA', 'HCV RNA', 'HBV DNA']), 'vl_parameter01',
'vl_hasil01',
'vl_hasil01teks',
'vl_satuan01',
'vl_rujukan01',
]),
initiallyExpanded: !_hasAny(['vl_parameter01', 'vl_hasil01']),
children: [
_select(
'vl_parameter01',
'Parameter',
_options('jsonvlparameter', const [
'HIV RNA',
'HCV RNA',
'HBV DNA',
]),
),
_select('vl_hasil01', 'Hasil', _viralLoadResultOptions),
_text('vl_hasil01teks', 'Hasil Manual'),
_select('vl_satuan01', 'Satuan', const ['copies/mL', 'IU/mL']),
_select('vl_rujukan01', 'Nilai Rujukan', const [
'40-10.000.000',
'10-100.000.000',
]),
],
),
CollapsibleExpertiseSection(
index: 2,
title: 'Parameter 02',
completed: _hasAny([
'vl_parameter02',
'vl_hasil02',
'vl_hasil02teks',
'vl_satuan02',
'vl_rujukan02',
]),
children: [
_select('vl_parameter02', 'Parameter 02', const ['LOG']),
_select('vl_hasil02', 'Hasil 02', _viralLoadLogResultOptions),
_text('vl_hasil02teks', 'Hasil 02 Manual'),
_select('vl_satuan02', 'Satuan 02', const ['LOG']),
_select('vl_rujukan02', 'Nilai Rujukan 02', const ['1,6-7', ' ']),
],
),
CollapsibleExpertiseSection(
index: 3,
title: 'Catatan Viral Load',
completed: _hasAny(['viralload']),
children: [
_text('viralload', 'Catatan Viral Load', minLines: 6, maxLines: 9),
],
), ),
_select('vl_hasil01', 'Hasil', _viralLoadResultOptions),
_text('vl_hasil01teks', 'Hasil Manual'),
_select('vl_satuan01', 'Satuan', const ['copies/mL', 'IU/mL']),
_select('vl_rujukan01', 'Nilai Rujukan', const [
'40-10.000.000',
'10-100.000.000',
]),
const SizedBox(height: 8),
_sectionTitle('Parameter 02', compact: true),
_select('vl_parameter02', 'Parameter 02', const ['LOG']),
_select('vl_hasil02', 'Hasil 02', _viralLoadLogResultOptions),
_text('vl_hasil02teks', 'Hasil 02 Manual'),
_select('vl_satuan02', 'Satuan 02', const ['LOG']),
_select('vl_rujukan02', 'Nilai Rujukan 02', const ['1,6-7', ' ']),
_text('viralload', 'Catatan Viral Load', minLines: 6, maxLines: 9),
], ],
); );
} }
bool _hasAny(List<String> names) {
for (final name in names) {
if ((widget.selectValues[name] ?? '').trim().isNotEmpty) {
return true;
}
if ((widget.textControllers[name]?.text ?? '').trim().isNotEmpty) {
return true;
}
}
return false;
}
Widget _antibioticSensitivity() { Widget _antibioticSensitivity() {
return AntibioticSensitivityPanel( return AntibioticSensitivityPanel(
textControllers: widget.textControllers, textControllers: widget.textControllers,