From d369d3754b83c8c85e506c53ff88029d84710f01 Mon Sep 17 00:00:00 2001 From: Dwi Swandhana Date: Tue, 4 Aug 2026 05:55:29 +0700 Subject: [PATCH] update --- .DS_Store | Bin 10244 -> 10244 bytes htdocs/.DS_Store | Bin 10244 -> 10244 bytes htdocs/public/high/css/bootstrap.min.css | 0 htdocs/public/high/css/icons.css | 0 htdocs/public/high/css/icons.css.map | 0 htdocs/public/high/css/style.css | 191 ++++++++--------- htdocs/public/high/css/style.css.map | 0 .../expertise/covid_expertise_wizard.dart | 67 ++++-- .../lib/screens/expertise/expertise_form.dart | 175 ++++++++++++++- .../screens/expertise/expertise_screen.dart | 199 +++++++++++++----- .../expertise/kultur_expertise_wizard.dart | 163 ++++++++++---- .../leptospira_expertise_wizard.dart | 58 ++++- .../pewarnaan_langsung_expertise_wizard.dart | 180 +++++++++++----- .../expertise/tbc_expertise_wizard.dart | 170 ++++++++++----- .../viral_load_expertise_wizard.dart | 87 ++++++-- 15 files changed, 936 insertions(+), 354 deletions(-) mode change 100644 => 100755 htdocs/public/high/css/bootstrap.min.css mode change 100644 => 100755 htdocs/public/high/css/icons.css mode change 100644 => 100755 htdocs/public/high/css/icons.css.map mode change 100644 => 100755 htdocs/public/high/css/style.css mode change 100644 => 100755 htdocs/public/high/css/style.css.map diff --git a/.DS_Store b/.DS_Store index 1662f77ae1759b6b58f3415dadb9cb644ba9ed07..e20b03c76ec7d55c7e64f2bafcaf45405cbd79bb 100644 GIT binary patch delta 302 zcmZn(XbG6$&nUhzU^hRb_+}n~3Puq_14|tRbCX&fg=#}%GeaE(3nRnHGXzVRgrq0) z35g5E3-A|b)3Puqlb5k7!V+)g79ffL3BLf`;6JxWJPBs_XWe_jGU!0L&?g-jL7Tym!HXf7A%mfup^sq}!!m{)3`ZGm zF}z{;#>m1b#;C-o&1k}C%V^K&!RW~t#hA>P!I-(ZUw94s=IPP}EE5Z2HnS`IWd{H; CIyXoF diff --git a/htdocs/.DS_Store b/htdocs/.DS_Store index f4843195eae153be4cad32c2b3935abdb2fa8fd0..5829495189b7f79a57c9174c6e65f3f3d4359ef6 100644 GIT binary patch delta 411 zcmZn(XbITBD>HewWT~*Bfu)Xuxk;^#Lbai>nW2t?h0)}_A|{hh$T(^XNz23w2qz^b zXQvnCm*%B7=jY@XIpvh5#tZNlXXKZACgc0i~vl;YM1~3 delta 433 zcmZn(XbITBE5o>bGM`MLns{}!k)ffEg1LFEjzYDip)rtYY*bsz$sww&Zygk$os*lF z-#Pi5h&*H0WlX@dCn0iOJdNMfs(9DbD#h`9)4SrKu5_$@zJ~nN_KiO=Wi( z#0&5jXXKZA0>uJ~Qp++^%cIkaGE;&J5|cCY(v|H$Fo1!wLjZ&^nNLo`KlK5a6Oxyo zm&(bh?BETNW8h%WW^iQiVhCo)U?^wkW0=LTj9~}EQHEO#Zy3HYvM`DDrtqCbPe@o$L{L;zR7_k#a%lOWf)N0^0C55U diff --git a/htdocs/public/high/css/bootstrap.min.css b/htdocs/public/high/css/bootstrap.min.css old mode 100644 new mode 100755 diff --git a/htdocs/public/high/css/icons.css b/htdocs/public/high/css/icons.css old mode 100644 new mode 100755 diff --git a/htdocs/public/high/css/icons.css.map b/htdocs/public/high/css/icons.css.map old mode 100644 new mode 100755 diff --git a/htdocs/public/high/css/style.css b/htdocs/public/high/css/style.css old mode 100644 new mode 100755 index c52c53bd..ce9fe27a --- a/htdocs/public/high/css/style.css +++ b/htdocs/public/high/css/style.css @@ -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 { diff --git a/htdocs/public/high/css/style.css.map b/htdocs/public/high/css/style.css.map old mode 100644 new mode 100755 diff --git a/mylis/lib/screens/expertise/covid_expertise_wizard.dart b/mylis/lib/screens/expertise/covid_expertise_wizard.dart index d763c851..9a23748b 100644 --- a/mylis/lib/screens/expertise/covid_expertise_wizard.dart +++ b/mylis/lib/screens/expertise/covid_expertise_wizard.dart @@ -107,31 +107,60 @@ class _CovidExpertiseWizardState extends State { 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 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, diff --git a/mylis/lib/screens/expertise/expertise_form.dart b/mylis/lib/screens/expertise/expertise_form.dart index d4bee139..76af8c5b 100644 --- a/mylis/lib/screens/expertise/expertise_form.dart +++ b/mylis/lib/screens/expertise/expertise_form.dart @@ -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(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 children; + final bool completed; + final bool initiallyExpanded; + final Color? color; + + @override + State createState() => + _CollapsibleExpertiseSectionState(); +} + +class _CollapsibleExpertiseSectionState + extends State { + 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, diff --git a/mylis/lib/screens/expertise/expertise_screen.dart b/mylis/lib/screens/expertise/expertise_screen.dart index a075080a..a37719b4 100644 --- a/mylis/lib/screens/expertise/expertise_screen.dart +++ b/mylis/lib/screens/expertise/expertise_screen.dart @@ -189,16 +189,6 @@ class _ExpertiseScreenState extends State { @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>( future: _future, builder: (context, snapshot) { @@ -217,41 +207,9 @@ class _ExpertiseScreenState extends State { _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 { 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 { } 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 { ); } } + +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 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, + ), + ), + ], + ), + ); + } +} diff --git a/mylis/lib/screens/expertise/kultur_expertise_wizard.dart b/mylis/lib/screens/expertise/kultur_expertise_wizard.dart index 2784b3e2..b323d5b7 100644 --- a/mylis/lib/screens/expertise/kultur_expertise_wizard.dart +++ b/mylis/lib/screens/expertise/kultur_expertise_wizard.dart @@ -116,64 +116,137 @@ class _KulturExpertiseWizardState extends State { 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 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 {}).isNotEmpty) { + return true; + } + } + return false; + } + Widget _saveDirectSmearButton() { return Padding( padding: const EdgeInsets.only(top: 4, bottom: 12), diff --git a/mylis/lib/screens/expertise/leptospira_expertise_wizard.dart b/mylis/lib/screens/expertise/leptospira_expertise_wizard.dart index bd1faa63..5e9303d9 100644 --- a/mylis/lib/screens/expertise/leptospira_expertise_wizard.dart +++ b/mylis/lib/screens/expertise/leptospira_expertise_wizard.dart @@ -108,20 +108,58 @@ class _LeptospiraExpertiseWizardState extends State { 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 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, diff --git a/mylis/lib/screens/expertise/pewarnaan_langsung_expertise_wizard.dart b/mylis/lib/screens/expertise/pewarnaan_langsung_expertise_wizard.dart index 61cfec9c..4d345245 100644 --- a/mylis/lib/screens/expertise/pewarnaan_langsung_expertise_wizard.dart +++ b/mylis/lib/screens/expertise/pewarnaan_langsung_expertise_wizard.dart @@ -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 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 {}).isNotEmpty) { + return true; + } + } + return false; + } + Widget _saveDirectSmearButton() { return Padding( padding: const EdgeInsets.only(top: 4, bottom: 12), diff --git a/mylis/lib/screens/expertise/tbc_expertise_wizard.dart b/mylis/lib/screens/expertise/tbc_expertise_wizard.dart index afde1765..d1542228 100644 --- a/mylis/lib/screens/expertise/tbc_expertise_wizard.dart +++ b/mylis/lib/screens/expertise/tbc_expertise_wizard.dart @@ -119,71 +119,135 @@ class _TbcExpertiseWizardState extends State { 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 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 {}).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, + ), + ], + ), ], ); } diff --git a/mylis/lib/screens/expertise/viral_load_expertise_wizard.dart b/mylis/lib/screens/expertise/viral_load_expertise_wizard.dart index 29d2e7f1..38846046 100644 --- a/mylis/lib/screens/expertise/viral_load_expertise_wizard.dart +++ b/mylis/lib/screens/expertise/viral_load_expertise_wizard.dart @@ -110,31 +110,78 @@ class _ViralLoadExpertiseWizardState extends State { 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 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,