update
This commit is contained in:
Vendored
BIN
Binary file not shown.
Regular → Executable
Regular → Executable
Regular → Executable
+87
-104
@@ -158,7 +158,7 @@ File: Main Css File
|
||||
}
|
||||
|
||||
#topnav .navbar-custom {
|
||||
background-color: #FEF0E1;
|
||||
background-color: #02c0ce;
|
||||
}
|
||||
|
||||
#topnav .navbar-toggle {
|
||||
@@ -174,7 +174,7 @@ File: Main Css File
|
||||
}
|
||||
|
||||
#topnav .navbar-toggle:hover span {
|
||||
background-color: #FEF0E1;
|
||||
background-color: #313a46;
|
||||
}
|
||||
|
||||
#topnav .navbar-toggle:focus {
|
||||
@@ -182,7 +182,7 @@ File: Main Css File
|
||||
}
|
||||
|
||||
#topnav .navbar-toggle:focus span {
|
||||
background-color: #FEF0E1;
|
||||
background-color: #313a46;
|
||||
}
|
||||
|
||||
#topnav .navbar-toggle .lines {
|
||||
@@ -240,72 +240,40 @@ File: Main Css File
|
||||
}
|
||||
|
||||
#topnav .navigation-menu > li > a {
|
||||
display: flex; /* Menggunakan Flexbox untuk alignment */
|
||||
align-items: center; /* Membuat item (ikon + teks) rata tengah secara vertikal */
|
||||
justify-content: center; /* Membuat item (ikon + teks) rata tengah secara horizontal */
|
||||
color: #2F2F2F;
|
||||
display: block;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
-webkit-transition: all .3s ease;
|
||||
transition: all .3s ease;
|
||||
-webkit-transition: all .5s ease;
|
||||
transition: all .5s ease;
|
||||
line-height: 20px;
|
||||
padding: 20px 25px;
|
||||
border-radius: 10px;
|
||||
min-width: 130px; /* Atur lebar minimum untuk semua menu */
|
||||
padding-left: 25px;
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
#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 {
|
||||
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;
|
||||
color: #2F2F2F;
|
||||
-webkit-transition: all .35s ease;
|
||||
transition: all .35s ease;
|
||||
margin-right: 8px;
|
||||
-webkit-transition: all .5s ease;
|
||||
transition: all .5s ease;
|
||||
}
|
||||
/* ATURAN HOVER YANG DIPERBAIKI */
|
||||
#topnav .navigation-menu .submenu li a:hover,
|
||||
#topnav .navigation-menu .submenu li a:focus,
|
||||
#topnav .navigation-menu .submenu li a:active {
|
||||
background-color: #FFA14F; /* Warna hover submenu */
|
||||
color: #2F2F2F;
|
||||
|
||||
#topnav .navigation-menu > li > a:hover, #topnav .navigation-menu > li > a:focus {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/*
|
||||
Responsive Menu
|
||||
*/
|
||||
@@ -328,16 +296,33 @@ File: Main Css File
|
||||
margin-right: 10px;
|
||||
}
|
||||
#topnav .navigation-menu > li:hover a {
|
||||
color: #2F2F2F !important;
|
||||
color: #ffffff;
|
||||
}
|
||||
#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: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 {
|
||||
white-space: nowrap;
|
||||
width: auto;
|
||||
@@ -393,7 +378,7 @@ File: Main Css File
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
font-weight: 500;
|
||||
background-color: #FEF0E1; /* Background submenu */
|
||||
color: #313a46;
|
||||
}
|
||||
#topnav .navbar-toggle {
|
||||
display: none;
|
||||
@@ -413,11 +398,10 @@ File: Main Css File
|
||||
#topnav .navigation-menu {
|
||||
float: none;
|
||||
max-height: 400px;
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
}
|
||||
#topnav .navigation-menu > li {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
#topnav .navigation-menu > li > a {
|
||||
color: #313a46;
|
||||
@@ -446,8 +430,7 @@ File: Main Css File
|
||||
color: #313a46;
|
||||
}
|
||||
#topnav .navigation-menu > li .submenu li a:hover {
|
||||
background-color: #FFA14F;
|
||||
color: #313a46;
|
||||
color: #02c0ce;
|
||||
}
|
||||
#topnav .navigation-menu > li .submenu li.has-submenu > a:after {
|
||||
content: "\F140";
|
||||
@@ -502,10 +485,10 @@ File: Main Css File
|
||||
overflow-y: auto;
|
||||
}
|
||||
#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 {
|
||||
color: #FFA14F;
|
||||
color: #02c0ce;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1131,11 +1114,11 @@ code {
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
background-color: #FFA14F !important;
|
||||
background-color: #2d7bf4 !important;
|
||||
}
|
||||
|
||||
.bg-success {
|
||||
background-color: #77C470 !important;
|
||||
background-color: #0acf97 !important;
|
||||
}
|
||||
|
||||
.bg-info {
|
||||
@@ -1143,11 +1126,11 @@ code {
|
||||
}
|
||||
|
||||
.bg-warning {
|
||||
background-color: #FAA645 !important;
|
||||
background-color: #f9bc0b !important;
|
||||
}
|
||||
|
||||
.bg-danger {
|
||||
background-color: #E8563C !important;
|
||||
background-color: #f1556c !important;
|
||||
}
|
||||
|
||||
.bg-muted {
|
||||
@@ -1196,7 +1179,7 @@ code {
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #FAA645 !important;
|
||||
color: #f9bc0b !important;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
@@ -1233,7 +1216,7 @@ code {
|
||||
}
|
||||
|
||||
.border-warning {
|
||||
border-color: #FAA645 !important;
|
||||
border-color: #f9bc0b !important;
|
||||
}
|
||||
|
||||
.border-danger {
|
||||
@@ -1265,12 +1248,12 @@ code {
|
||||
}
|
||||
|
||||
.badge-custom {
|
||||
background: #FFA14F;
|
||||
background: #02c0ce;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
background-color: #FFA14F;
|
||||
background-color: #2d7bf4;
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
@@ -1286,7 +1269,7 @@ code {
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background-color: #FAA645;
|
||||
background-color: #f9bc0b;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
@@ -1679,15 +1662,15 @@ code {
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background-color: #FAA645;
|
||||
border: 1px solid #FAA645;
|
||||
background-color: #f9bc0b;
|
||||
border: 1px solid #f9bc0b;
|
||||
}
|
||||
|
||||
.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:not([disabled]):not(.disabled):active, .show > .btn-warning.dropdown-toggle {
|
||||
background-color: #FAA645;
|
||||
border-color: #FAA645;
|
||||
background-color: #e5ac06;
|
||||
border-color: #e5ac06;
|
||||
}
|
||||
|
||||
.btn-warning.focus, .btn-warning:focus, .btn-outline-warning.focus, .btn-outline-warning:focus,
|
||||
@@ -1802,13 +1785,13 @@ code {
|
||||
}
|
||||
|
||||
.btn-outline-warning {
|
||||
color: #FAA645;
|
||||
border-color: #FAA645;
|
||||
color: #f9bc0b;
|
||||
border-color: #f9bc0b;
|
||||
}
|
||||
|
||||
.btn-outline-warning:hover {
|
||||
background-color: #FAA645;
|
||||
border-color: #FAA645;
|
||||
background-color: #f9bc0b;
|
||||
border-color: #f9bc0b;
|
||||
}
|
||||
|
||||
.btn-outline-danger {
|
||||
@@ -2000,7 +1983,7 @@ code {
|
||||
}
|
||||
|
||||
.ribbon-box .ribbon-warning {
|
||||
background: #FAA645;
|
||||
background: #f9bc0b;
|
||||
}
|
||||
|
||||
.ribbon-box .ribbon-warning:before {
|
||||
@@ -2143,7 +2126,7 @@ code {
|
||||
}
|
||||
|
||||
.ribbon-box .ribbon-two-warning span {
|
||||
background: #FAA645;
|
||||
background: #f9bc0b;
|
||||
}
|
||||
|
||||
.ribbon-box .ribbon-two-warning span:before {
|
||||
@@ -2426,8 +2409,8 @@ code {
|
||||
}
|
||||
|
||||
.checkbox-warning input[type="checkbox"]:checked + label::before {
|
||||
background-color: #FAA645;
|
||||
border-color: #FAA645;
|
||||
background-color: #f9bc0b;
|
||||
border-color: #f9bc0b;
|
||||
}
|
||||
|
||||
.checkbox-warning input[type="checkbox"]:checked + label::after {
|
||||
@@ -2601,15 +2584,15 @@ code {
|
||||
}
|
||||
|
||||
.radio-warning input[type="radio"] + label::after {
|
||||
background-color: #FAA645;
|
||||
background-color: #f9bc0b;
|
||||
}
|
||||
|
||||
.radio-warning input[type="radio"]:checked + label::before {
|
||||
border-color: #FAA645;
|
||||
border-color: #f9bc0b;
|
||||
}
|
||||
|
||||
.radio-warning input[type="radio"]:checked + label::after {
|
||||
background-color: #FAA645;
|
||||
background-color: #f9bc0b;
|
||||
}
|
||||
|
||||
.radio-success input[type="radio"] + label::after {
|
||||
@@ -2682,7 +2665,7 @@ code {
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
color: #FAA645;
|
||||
color: #f9bc0b;
|
||||
background-color: #fffaeb;
|
||||
border-color: #fbd76f;
|
||||
}
|
||||
@@ -2741,9 +2724,9 @@ code {
|
||||
}
|
||||
|
||||
.jq-icon-warning {
|
||||
background-color: #FAA645;
|
||||
background-color: #f9bc0b;
|
||||
color: #ffffff;
|
||||
border-color: #FAA645;
|
||||
border-color: #f9bc0b;
|
||||
}
|
||||
|
||||
.jq-icon-error {
|
||||
@@ -3146,7 +3129,7 @@ code {
|
||||
}
|
||||
|
||||
.progress-bar-warning {
|
||||
background-color: #FAA645;
|
||||
background-color: #f9bc0b;
|
||||
}
|
||||
|
||||
.progress-bar-danger {
|
||||
@@ -3280,8 +3263,8 @@ code {
|
||||
}
|
||||
|
||||
.swal2-icon.swal2-warning {
|
||||
color: #FAA645;
|
||||
border-color: #FAA645;
|
||||
color: #f9bc0b;
|
||||
border-color: #f9bc0b;
|
||||
}
|
||||
|
||||
.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 {
|
||||
stroke: #FAA645;
|
||||
stroke: #f9bc0b;
|
||||
}
|
||||
|
||||
.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 {
|
||||
border-color: #FAA645;
|
||||
border-color: #f9bc0b;
|
||||
}
|
||||
|
||||
.message-list li.green-dot .col-mail-1 .dot {
|
||||
|
||||
Regular → Executable
@@ -107,31 +107,60 @@ class _CovidExpertiseWizardState extends State<CovidExpertiseWizard> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_sectionTitle('PCR COVID'),
|
||||
for (final virus in _covidVirusFields)
|
||||
Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_sectionTitle(virus.label, compact: true),
|
||||
_select(virus.resultField, virus.label, _covidResultOptions),
|
||||
_text(virus.unitField, 'Satuan'),
|
||||
_text(virus.referenceField, 'Nilai Rujukan'),
|
||||
],
|
||||
for (var i = 0; i < _covidVirusFields.length; i += 1)
|
||||
CollapsibleExpertiseSection(
|
||||
index: i + 1,
|
||||
title: _covidVirusFields[i].label,
|
||||
completed: _hasAny([
|
||||
_covidVirusFields[i].resultField,
|
||||
_covidVirusFields[i].unitField,
|
||||
_covidVirusFields[i].referenceField,
|
||||
]),
|
||||
initiallyExpanded:
|
||||
i == 0 &&
|
||||
!_hasAny([
|
||||
_covidVirusFields[i].resultField,
|
||||
_covidVirusFields[i].unitField,
|
||||
]),
|
||||
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() {
|
||||
return AntibioticSensitivityPanel(
|
||||
textControllers: widget.textControllers,
|
||||
|
||||
@@ -27,7 +27,7 @@ class ExpertiseWizardShell extends StatelessWidget {
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 10),
|
||||
padding: const EdgeInsets.fromLTRB(18, 18, 18, 10),
|
||||
child: _WizardHeader(
|
||||
currentStep: safeStep,
|
||||
steps: steps,
|
||||
@@ -40,14 +40,17 @@ class ExpertiseWizardShell extends StatelessWidget {
|
||||
duration: const Duration(milliseconds: 180),
|
||||
child: Padding(
|
||||
key: ValueKey<int>(safeStep),
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 18),
|
||||
padding: const EdgeInsets.fromLTRB(18, 0, 18, 18),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleLarge
|
||||
?.copyWith(fontWeight: FontWeight.w800),
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: const Color(0xFF080D3D),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Align(
|
||||
@@ -99,7 +102,7 @@ class _WizardHeader extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 46,
|
||||
height: 50,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: steps.length,
|
||||
@@ -113,6 +116,19 @@ class _WizardHeader extends StatelessWidget {
|
||||
onSelected: (_) => onStepChanged(index),
|
||||
showCheckmark: false,
|
||||
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: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: canPrevious ? onPrevious : null,
|
||||
icon: const Icon(Icons.chevron_left),
|
||||
icon: const Icon(Icons.chevron_left_rounded),
|
||||
label: const Text('Previous'),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -178,7 +194,7 @@ class _WizardBottomToolbar extends StatelessWidget {
|
||||
const SizedBox(width: 12),
|
||||
FilledButton.icon(
|
||||
onPressed: canNext ? onNext : null,
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
icon: const Icon(Icons.chevron_right_rounded),
|
||||
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 {
|
||||
const CompactMultiSelectField({
|
||||
super.key,
|
||||
|
||||
@@ -189,16 +189,6 @@ class _ExpertiseScreenState extends State<ExpertiseScreen> {
|
||||
@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) {
|
||||
@@ -217,41 +207,9 @@ class _ExpertiseScreenState extends State<ExpertiseScreen> {
|
||||
_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 summaryCard = _ExpertiseSummaryCard(
|
||||
item: item,
|
||||
dlp: _currentDlp,
|
||||
);
|
||||
|
||||
final expertiseContent = _currentDlp.isEmpty
|
||||
@@ -426,8 +384,18 @@ class _ExpertiseScreenState extends State<ExpertiseScreen> {
|
||||
|
||||
if (_currentDlp.isEmpty) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.fromLTRB(18, 18, 18, 24),
|
||||
children: [
|
||||
SafeArea(
|
||||
bottom: false,
|
||||
child: _ExpertiseHeader(
|
||||
title: 'Expertise',
|
||||
subtitle: 'Pilih Template',
|
||||
saving: _saving,
|
||||
onRefresh: _reload,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
summaryCard,
|
||||
const SizedBox(height: 14),
|
||||
expertiseContent,
|
||||
@@ -436,9 +404,19 @@ class _ExpertiseScreenState extends State<ExpertiseScreen> {
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||
padding: const EdgeInsets.fromLTRB(18, 18, 18, 0),
|
||||
child: Column(
|
||||
children: [
|
||||
SafeArea(
|
||||
bottom: false,
|
||||
child: _ExpertiseHeader(
|
||||
title: 'Expertise',
|
||||
subtitle: _currentDlp,
|
||||
saving: _saving,
|
||||
onRefresh: _reload,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
summaryCard,
|
||||
const SizedBox(height: 14),
|
||||
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(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_sectionTitle('A. Sediaan Langsung'),
|
||||
_sectionTitle('1. Pewarna Gram', compact: true),
|
||||
_select('id_selepitel', 'Sel Epitel', _options('jsonselepitel')),
|
||||
_select('id_selradang', 'Sel Radang', _options('jsonselradang')),
|
||||
_select(
|
||||
'id_mikroorganisme',
|
||||
'Mikroorganisme',
|
||||
_options('jsonmikroorganisme'),
|
||||
),
|
||||
_multiSelect(
|
||||
'id_mikroorganismeoptional',
|
||||
'Bisa memilih lebih dari satu',
|
||||
_options('jsonmikroorganismeoptional'),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 1,
|
||||
title: 'Pewarnaan Gram',
|
||||
completed: _hasAny([
|
||||
'id_selepitel',
|
||||
'id_selradang',
|
||||
'id_mikroorganisme',
|
||||
'id_mikroorganismeoptional',
|
||||
]),
|
||||
initiallyExpanded: !_hasAny(['id_selepitel', 'id_selradang']),
|
||||
children: [
|
||||
_select('id_selepitel', 'Sel Epitel', _options('jsonselepitel')),
|
||||
_select('id_selradang', 'Sel Radang', _options('jsonselradang')),
|
||||
_select(
|
||||
'id_mikroorganisme',
|
||||
'Mikroorganisme',
|
||||
_options('jsonmikroorganisme'),
|
||||
),
|
||||
_multiSelect(
|
||||
'id_mikroorganismeoptional',
|
||||
'Bisa memilih lebih dari satu',
|
||||
_options('jsonmikroorganismeoptional'),
|
||||
),
|
||||
],
|
||||
),
|
||||
_nugentScore(),
|
||||
_sectionTitle('2. Pewarnaan Ziehl Neelsen', compact: true),
|
||||
_select(
|
||||
'id_pewarnaanziehlnielsen',
|
||||
'Pagi',
|
||||
_options('jsonpewarnaanziehlnielsen'),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 2,
|
||||
title: 'Pewarnaan Ziehl Neelsen',
|
||||
completed: _hasAny([
|
||||
'id_pewarnaanziehlnielsen',
|
||||
'id_pewarnaanziehlnielsensewaktu',
|
||||
]),
|
||||
children: [
|
||||
_select(
|
||||
'id_pewarnaanziehlnielsen',
|
||||
'Pagi',
|
||||
_options('jsonpewarnaanziehlnielsen'),
|
||||
),
|
||||
_select(
|
||||
'id_pewarnaanziehlnielsensewaktu',
|
||||
'Sewaktu',
|
||||
_options('jsonpewarnaanziehlnielsen'),
|
||||
),
|
||||
],
|
||||
),
|
||||
_select(
|
||||
'id_pewarnaanziehlnielsensewaktu',
|
||||
'Sewaktu',
|
||||
_options('jsonpewarnaanziehlnielsen'),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 3,
|
||||
title: 'Pewarnaan KOH',
|
||||
completed: _hasAny(['id_pewarnaankoh', 'id_pewarnaankohoptional']),
|
||||
children: [
|
||||
_select(
|
||||
'id_pewarnaankoh',
|
||||
'Pewarnaan KOH',
|
||||
_options('jsonpewarnaankoh', _kohFallback),
|
||||
),
|
||||
_text('id_pewarnaankohoptional', 'Pewarnaan KOH Manual'),
|
||||
],
|
||||
),
|
||||
_select(
|
||||
'id_pewarnaankoh',
|
||||
'3. Pewarnaan KOH',
|
||||
_options('jsonpewarnaankoh', _kohFallback),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 4,
|
||||
title: 'Pewarnaan Neisser',
|
||||
completed: _hasAny(['id_pewarnaanneisser']),
|
||||
children: [
|
||||
_select(
|
||||
'id_pewarnaanneisser',
|
||||
'Pewarnaan Neisser',
|
||||
_options('jsonpewarnaanneisser'),
|
||||
),
|
||||
],
|
||||
),
|
||||
_text('id_pewarnaankohoptional', 'Pewarnaan KOH Manual'),
|
||||
_select(
|
||||
'id_pewarnaanneisser',
|
||||
'4. Pewarnaan Neisser',
|
||||
_options('jsonpewarnaanneisser'),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 5,
|
||||
title: 'Pewarnaan Negatif',
|
||||
completed: _hasAny(['id_pewarnaannegatif']),
|
||||
children: [
|
||||
_select(
|
||||
'id_pewarnaannegatif',
|
||||
'Pewarnaan Negatif',
|
||||
_options('jsonpewarnaannegatif'),
|
||||
),
|
||||
],
|
||||
),
|
||||
_select(
|
||||
'id_pewarnaannegatif',
|
||||
'5. Pewarnaan Negatif',
|
||||
_options('jsonpewarnaannegatif'),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 6,
|
||||
title: 'Pewarnaan Spora',
|
||||
completed: _hasAny(['id_pewarnaanspora']),
|
||||
children: [
|
||||
_select(
|
||||
'id_pewarnaanspora',
|
||||
'Pewarnaan Spora',
|
||||
_options('jsonpewarnaanspora'),
|
||||
),
|
||||
],
|
||||
),
|
||||
_select(
|
||||
'id_pewarnaanspora',
|
||||
'6. Pewarnaan Spora',
|
||||
_options('jsonpewarnaanspora'),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 7,
|
||||
title: 'Pewarnaan Giemsa',
|
||||
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(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
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() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 4, bottom: 12),
|
||||
|
||||
@@ -108,20 +108,58 @@ class _LeptospiraExpertiseWizardState extends State<LeptospiraExpertiseWizard> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_sectionTitle('IgM IgG Leptospira'),
|
||||
_select('igg_parameter', 'IgG Leptospira', const [
|
||||
'Positif',
|
||||
'Negatif',
|
||||
]),
|
||||
_select('igm_parameter', 'IgM Leptospira', const [
|
||||
'Positif',
|
||||
'Negatif',
|
||||
]),
|
||||
_text('iggigm_interpretasi', 'Interpretasi', minLines: 6, maxLines: 9),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 1,
|
||||
title: 'IgG Leptospira',
|
||||
completed: _hasAny(['igg_parameter']),
|
||||
initiallyExpanded: !_hasAny(['igg_parameter', 'igm_parameter']),
|
||||
children: [
|
||||
_select('igg_parameter', 'IgG Leptospira', const [
|
||||
'Positif',
|
||||
'Negatif',
|
||||
]),
|
||||
],
|
||||
),
|
||||
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() {
|
||||
return AntibioticSensitivityPanel(
|
||||
textControllers: widget.textControllers,
|
||||
|
||||
@@ -113,63 +113,128 @@ class _PewarnaanLangsungExpertiseWizardState
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_sectionTitle('A. Sediaan Langsung'),
|
||||
_sectionTitle('1. Pewarnaan Gram', compact: true),
|
||||
_select('lsg_selepitel', 'Sel Epitel', _options('jsonselepitel')),
|
||||
_select('lsg_selradang', 'Sel Radang', _options('jsonselradang')),
|
||||
_select(
|
||||
'lsg_mikroorganisme',
|
||||
'Mikroorganisme',
|
||||
_options('jsonmikroorganisme'),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 1,
|
||||
title: 'Pewarnaan Gram',
|
||||
completed: _hasAny([
|
||||
'lsg_selepitel',
|
||||
'lsg_selradang',
|
||||
'lsg_mikroorganisme',
|
||||
'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(
|
||||
'lsg_mikroorganismeoptional',
|
||||
'Bisa memilih lebih dari satu',
|
||||
_options('jsonmikroorganismeoptional'),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 2,
|
||||
title: 'Pewarnaan Ziehl Neelsen',
|
||||
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),
|
||||
_select(
|
||||
'lsg_pewarnaanziehlnielsen',
|
||||
'Pagi',
|
||||
_options('jsonpewarnaanziehlnielsen'),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 3,
|
||||
title: 'Pewarnaan KOH',
|
||||
completed: _hasAny(['lsg_pewarnaankoh', 'lsg_pewarnaankohoptional']),
|
||||
children: [
|
||||
_select(
|
||||
'lsg_pewarnaankoh',
|
||||
'Pewarnaan KOH',
|
||||
_options('jsonpewarnaankoh', _kohFallback),
|
||||
),
|
||||
_text('lsg_pewarnaankohoptional', 'Pewarnaan KOH Manual'),
|
||||
],
|
||||
),
|
||||
_select(
|
||||
'lsg_pewarnaanziehlnielsensewaktu',
|
||||
'Sewaktu',
|
||||
_options('jsonpewarnaanziehlnielsen'),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 4,
|
||||
title: 'Pewarnaan Neisser',
|
||||
completed: _hasAny(['lsg_pewarnaanneisser']),
|
||||
children: [
|
||||
_select(
|
||||
'lsg_pewarnaanneisser',
|
||||
'Pewarnaan Neisser',
|
||||
_options('jsonpewarnaanneisser'),
|
||||
),
|
||||
],
|
||||
),
|
||||
_select(
|
||||
'lsg_pewarnaankoh',
|
||||
'3. Pewarnaan KOH',
|
||||
_options('jsonpewarnaankoh', _kohFallback),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 5,
|
||||
title: 'Pewarnaan Negatif',
|
||||
completed: _hasAny(['lsg_pewarnaannegatif']),
|
||||
children: [
|
||||
_select(
|
||||
'lsg_pewarnaannegatif',
|
||||
'Pewarnaan Negatif',
|
||||
_options('jsonpewarnaannegatif'),
|
||||
),
|
||||
],
|
||||
),
|
||||
_text('lsg_pewarnaankohoptional', 'Pewarnaan KOH Manual'),
|
||||
_select(
|
||||
'lsg_pewarnaanneisser',
|
||||
'4. Pewarnaan Neisser',
|
||||
_options('jsonpewarnaanneisser'),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 6,
|
||||
title: 'Pewarnaan Spora',
|
||||
completed: _hasAny(['lsg_pewarnaanspora']),
|
||||
children: [
|
||||
_select(
|
||||
'lsg_pewarnaanspora',
|
||||
'Pewarnaan Spora',
|
||||
_options('jsonpewarnaanspora'),
|
||||
),
|
||||
],
|
||||
),
|
||||
_select(
|
||||
'lsg_pewarnaannegatif',
|
||||
'5. Pewarnaan Negatif',
|
||||
_options('jsonpewarnaannegatif'),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 7,
|
||||
title: 'Pewarnaan Giemsa',
|
||||
completed: _hasAny([
|
||||
'lsg_pewarnaangiesma',
|
||||
'lsg_pewarnaangiesmaoptional',
|
||||
]),
|
||||
children: [
|
||||
_select(
|
||||
'lsg_pewarnaangiesma',
|
||||
'Pewarnaan Giemsa',
|
||||
_options('jsonpewarnaangiemsa', _giemsaFallback),
|
||||
),
|
||||
_text('lsg_pewarnaangiesmaoptional', 'Pewarnaan Giemsa Manual'),
|
||||
],
|
||||
),
|
||||
_select(
|
||||
'lsg_pewarnaangiesma',
|
||||
'6. Pewarnaan Giemsa',
|
||||
_options('jsonpewarnaangiemsa', _giemsaFallback),
|
||||
),
|
||||
_text('lsg_pewarnaangiesmaoptional', 'Pewarnaan Giemsa Manual'),
|
||||
_select(
|
||||
'lsg_pewarnaanspora',
|
||||
'7. Pewarnaan Spora',
|
||||
_options('jsonpewarnaanspora'),
|
||||
),
|
||||
_text(
|
||||
'lsg_pewarnaanlain',
|
||||
'8. Pewarnaan Lain-Lain',
|
||||
minLines: 5,
|
||||
maxLines: 8,
|
||||
CollapsibleExpertiseSection(
|
||||
index: 8,
|
||||
title: 'Pewarnaan Lain-Lain',
|
||||
completed: _hasAny(['lsg_pewarnaanlain']),
|
||||
children: [
|
||||
_text(
|
||||
'lsg_pewarnaanlain',
|
||||
'Pewarnaan Lain-Lain',
|
||||
minLines: 5,
|
||||
maxLines: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
_nugentScore(),
|
||||
_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() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 4, bottom: 12),
|
||||
|
||||
@@ -119,71 +119,135 @@ class _TbcExpertiseWizardState extends State<TbcExpertiseWizard> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_sectionTitle('Jenis Terduga / Pasien TBC'),
|
||||
_multiSelect('jenis_pasien_tbc', 'Jenis Terduga / Pasien TBC', const [
|
||||
'TBC SO',
|
||||
'TBC RO',
|
||||
'Anak',
|
||||
'HIV',
|
||||
'DM',
|
||||
]),
|
||||
_text('id_noidsediaan', 'No. Identitas Sediaan'),
|
||||
_text(
|
||||
'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',
|
||||
CollapsibleExpertiseSection(
|
||||
index: 1,
|
||||
title: 'Jenis Terduga / Pasien TBC',
|
||||
completed: _hasAny([
|
||||
'jenis_pasien_tbc',
|
||||
'id_noidsediaan',
|
||||
'id_tanggalpengambilancontohuji',
|
||||
'id_tanggalpengirimancontohuji',
|
||||
'id_lamariwayatpengobatantb',
|
||||
]),
|
||||
initiallyExpanded: !_hasAny(['jenis_pasien_tbc', 'id_noidsediaan']),
|
||||
children: [
|
||||
_multiSelect(
|
||||
'jenis_pasien_tbc',
|
||||
'Jenis Terduga / Pasien TBC',
|
||||
const ['TBC SO', 'TBC RO', 'Anak', 'HIV', 'DM'],
|
||||
),
|
||||
_text('id_noidsediaan', 'No. Identitas Sediaan'),
|
||||
_text(
|
||||
'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 [
|
||||
'Paru',
|
||||
'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)',
|
||||
CollapsibleExpertiseSection(
|
||||
index: 2,
|
||||
title: 'Identitas Nasional',
|
||||
completed: _hasAny([
|
||||
'id_lokasianatomi',
|
||||
'id_alasanpemeriksaan',
|
||||
'id_noregfasyankes',
|
||||
'id_noregkota',
|
||||
]),
|
||||
children: [
|
||||
_select('id_lokasianatomi', 'Lokasi Anatomi', const [
|
||||
'Paru',
|
||||
'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)'),
|
||||
_text(
|
||||
'id_bulanpemeriksaansetelahselesai',
|
||||
'Pemeriksaan setelah selesai pengobatan (Bulan ke)',
|
||||
CollapsibleExpertiseSection(
|
||||
index: 3,
|
||||
title: 'Contoh Uji dan Visual Dahak',
|
||||
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() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_sectionTitle('Jenis Pemeriksaan'),
|
||||
_select('id_jenispemeriksaantb', 'Jenis Pemeriksaan', _tbcTestTypes),
|
||||
CollapsibleExpertiseSection(
|
||||
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(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_sectionTitle('Parameter Viral Load'),
|
||||
_select(
|
||||
'vl_parameter01',
|
||||
'Parameter',
|
||||
_options('jsonvlparameter', const ['HIV RNA', 'HCV RNA', 'HBV DNA']),
|
||||
CollapsibleExpertiseSection(
|
||||
index: 1,
|
||||
title: 'Parameter Viral Load',
|
||||
completed: _hasAny([
|
||||
'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() {
|
||||
return AntibioticSensitivityPanel(
|
||||
textControllers: widget.textControllers,
|
||||
|
||||
Reference in New Issue
Block a user