Squashed commit of the following:

commit bcfb4c1456
Merge: 1cbde57 975c87d
Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com>
Date:   Mon Nov 17 11:15:14 2025 +0700

    Merge pull request #147 from dikstub-rssa/feat/surat-kontrol-135

    Feat: Integration Rehab Medik - Surat Kontrol

commit 975c87d99a
Merge: f582090 1cbde57
Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com>
Date:   Mon Nov 17 10:58:10 2025 +0700

    Merge branch 'dev' into feat/surat-kontrol-135

commit 1cbde57cf9
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Sun Nov 16 00:44:53 2025 +0700

    dev: hotfix

    comps/pub/myui
    + updated data/types
    + updated data-table
    + updated nav-header
    + added toggle

    comps/pub/ui
    + updated button
    + updated toggle

commit ccabe0177b
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Fri Nov 14 16:39:21 2025 +0700

    dev: hotfix, added combobox objectsToItems

commit f582090d18
Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com>
Date:   Thu Nov 13 11:56:21 2025 +0700

    Fix: Refactor surat kontrol

commit 0d97ba9d25
Merge: 02508b2 bb8df3d
Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com>
Date:   Thu Nov 13 11:52:23 2025 +0700

    Merge pull request #164 from dikstub-rssa/feeat/pendaftaran-kemoterapi-141

    Feat: Pendaftaran Kemoterapi

commit bb8df3d53a
Merge: a592a0b 02508b2
Author: riefive <rie.five@gmail.com>
Date:   Thu Nov 13 10:14:17 2025 +0700

    Merge branch 'dev' of https://github.com/dikstub-rssa/simrs-fe into feeat/pendaftaran-kemoterapi-141

commit 02508b22de
Merge: 6b933de 295bb81
Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com>
Date:   Thu Nov 13 07:56:31 2025 +0700

    Merge pull request #162 from dikstub-rssa/fe-prescription-56

    Fe prescription 56

commit 295bb8120f
Merge: 8462eba 6b933de
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Thu Nov 13 07:45:48 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit 6b933de212
Merge: f2e98fc 471c846
Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com>
Date:   Wed Nov 12 07:13:11 2025 +0700

    Merge pull request #156 from dikstub-rssa/feat/cp-lab-order-48

    Feat/cp lab order 48

commit f2e98fc732
Merge: 2e899c6 9b281de
Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com>
Date:   Wed Nov 12 07:12:40 2025 +0700

    Merge pull request #158 from dikstub-rssa/feat/menu-structure

    Feat/menu structure

commit 471c846045
Merge: f676c8a 2e899c6
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Nov 12 07:11:46 2025 +0700

    Merge branch 'dev' into feat/cp-lab-order-48

commit 9b281de00b
Merge: 80383a5 2e899c6
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Nov 12 07:08:29 2025 +0700

    Merge branch 'dev' into feat/menu-structure

commit 2e899c6022
Merge: 8effefb b7d4fcf
Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com>
Date:   Wed Nov 12 07:04:47 2025 +0700

    Merge pull request #157 from dikstub-rssa/feat/encounter-status-107

    Feat/encounter status 107

commit 8effefb5ad
Merge: 3f63f19 8e7f9b1
Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com>
Date:   Wed Nov 12 07:04:17 2025 +0700

    Merge pull request #155 from dikstub-rssa/feat/radiology-order-54

    Feat/radiology order 54

commit 80383a5f0a
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Nov 12 06:52:31 2025 +0700

    feat/menu-structure: adjust page rehab

commit 93c9e74d08
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Nov 12 06:52:14 2025 +0700

    feat/menu-structure: adjust menu items all roles

commit f0d2bc4de1
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Nov 12 06:51:37 2025 +0700

    feat/menu-structure: update access control

commit 02c14089f1
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Nov 12 06:51:03 2025 +0700

    feat/menu-structure: update role switcher

commit a14c4a5d3c
Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com>
Date:   Tue Nov 11 14:21:58 2025 +0700

    Fix: Refactor Surat Kontrol CRUD {id} to {code}

commit e9e0e21d1b
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Tue Nov 11 12:30:43 2025 +0700

    feat/menu-structure: wip

commit 8e7f9b19e3
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Mon Nov 10 23:17:49 2025 +0700

    feat/radiology-order-54: upgraded mcu-order/list

commit 24313adef6
Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com>
Date:   Fri Nov 7 10:35:46 2025 +0700

    Fix: debug back btn in add, edit, detail content page

commit 59b44b5729
Merge: 99a61a0 db15ec9
Author: Muhammad Hasyim Chaidir Ali <68959522+Hasyim-Kai@users.noreply.github.com>
Date:   Fri Nov 7 09:11:10 2025 +0700

    Merge branch 'dev' into feat/surat-kontrol-135

commit 99a61a0bf2
Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com>
Date:   Thu Nov 6 08:06:01 2025 +0700

    Feat: add right & bottom label in input base component

commit 8462eba94b
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Nov 5 21:23:04 2025 +0700

    feat/prescription-56: wip

commit db48919325
Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com>
Date:   Wed Nov 5 13:53:43 2025 +0700

    Feat: add banner in List if requirement not met

commit bd57250f7e
Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com>
Date:   Wed Nov 5 13:26:48 2025 +0700

    Fix: refactor getDetail url param

commit a361922e32
Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com>
Date:   Wed Nov 5 13:19:07 2025 +0700

    Feat: Add & integrate add, edit, detail page

commit 331f4a6b20
Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com>
Date:   Tue Nov 4 16:56:08 2025 +0700

    Feat: Integrate Control Letter

commit a592a0be36
Author: riefive <rie.five@gmail.com>
Date:   Tue Nov 4 15:15:38 2025 +0700

    feat(cemo): add home encounter

commit be0a761170
Author: riefive <rie.five@gmail.com>
Date:   Tue Nov 4 13:23:52 2025 +0700

    feat(cemo): change flow admin

commit 64fe2524fb
Author: riefive <rie.five@gmail.com>
Date:   Tue Nov 4 12:02:31 2025 +0700

    feat(cemo): enhance admin mode functionality and update series handling

commit fb7731188d
Author: riefive <rie.five@gmail.com>
Date:   Mon Nov 3 15:52:35 2025 +0700

    feat(cemo): add mode adm + series

commit 89b2fb9cd9
Author: riefive <rie.five@gmail.com>
Date:   Mon Nov 3 15:03:56 2025 +0700

    feat(chemo): add page process and modify components

commit f676c8a4b9
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Nov 3 08:11:02 2025 +0700

    feat/cp-lab-order-48: wip

commit 69ffe6bd49
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Fri Oct 31 14:35:39 2025 +0700

    feat/radiology-order: added the page

commit d1369d513b
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 31 16:08:22 2025 +0700

    feat(cemo): add list verification

commit a9ab75fd98
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Fri Oct 31 14:35:05 2025 +0700

    feat/readiology-order: added mcu

commit 71d68e5a0e
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 31 14:49:21 2025 +0700

    feat(cemo): add dialog verification and list register

commit f8d906b6c2
Merge: 66872c9 5f9e441
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Fri Oct 31 14:48:21 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit 40d78a999a
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Fri Oct 31 14:35:05 2025 +0700

    feat/readiology-order: added mcu

commit b3502df0f8
Merge: 831749a 7119f67
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 31 13:12:11 2025 +0700

    Merge branch 'feat/fe-kemoterapi' into feeat/pendaftaran-kemoterapi-141

commit 7119f67402
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 31 13:09:59 2025 +0700

    feat(cemo): modify schema

commit 66872c95f8
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Fri Oct 31 07:56:36 2025 +0700

    feat/prescription-56: wip

commit 45cc019ec1
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 30 15:43:50 2025 +0700

    feat(cemo): layouting form

commit e866c0cf2a
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 30 14:41:52 2025 +0700

    feat(cemo): layouting protocol

commit dc4edc1dc0
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 29 15:58:52 2025 +0700

    feat(cemo): show list cemo

commit 3234853473
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 29 15:39:55 2025 +0700

    feat(cemo): add list of cemo

commit 67ee129f4b
Merge: 9919b4b 9e82d17
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Tue Oct 28 16:20:17 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit 2275f4dc99
Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com>
Date:   Mon Oct 27 14:01:58 2025 +0700

    Feat: add UI BPJS > Surat Kontrol

commit 89e0e7a2c8
Author: hasyim_kai <muhammad.hasyim.c.a@gmail.com>
Date:   Mon Oct 27 10:21:59 2025 +0700

    Feat: add UI CRUD Surat Kontrol at Rehab Medik > kunjungan > Proses

commit 9919b4b896
Merge: 19a43bd e93e72a
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sat Oct 25 15:36:29 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit b7d4fcf939
Merge: eaac4aa e93e72a
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sat Oct 25 15:31:30 2025 +0700

    Merge branch 'dev' into feat/encounter-status-107

commit 19a43bd291
Merge: d90e400 3558672
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sat Oct 25 05:02:57 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit eaac4aab85
Merge: 72e8d43 3558672
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sat Oct 25 05:01:45 2025 +0700

    Merge branch 'dev' into feat/encounter-status-107

commit d90e40043c
Merge: 0c9f9de b90f0c1
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Fri Oct 24 12:40:59 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit 0c9f9deb7e
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Fri Oct 24 12:38:02 2025 +0700

    fe-prescription-56: wip

commit 729474a2a0
Merge: 7159bd6 ddd35d6
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Thu Oct 23 14:16:52 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit 72e8d431d6
Merge: 3f77d92 2a9b78a
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Oct 22 07:17:41 2025 +0700

    Merge branch 'dev' into feat/encounter-status-107

commit 3f77d927b6
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Tue Oct 21 22:48:34 2025 +0700

    feat/encounter: done

commit d8c861d60c
Merge: 6bdee66 27ab7c2
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Tue Oct 21 00:15:01 2025 +0700

    Merge branch 'dev' into feat/encounter-status-107

commit 7159bd6566
Merge: ccc9b0b be5768b
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Oct 13 07:45:15 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit ccc9b0bda3
Merge: f94ccd7 cad7ac6
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Oct 13 06:29:03 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit 6bdee66cc6
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Oct 13 06:26:30 2025 +0700

    feat/encounter: wip

commit f7c53fc4e5
Merge: a7c7ef6 cad7ac6
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Oct 13 06:24:45 2025 +0700

    Merge branch 'dev' into feat/encounter-status-107

commit a7c7ef6dd8
Merge: 89b051b f52e516
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sun Oct 12 13:27:06 2025 +0700

    Merge branch 'dev' into feat/encounter-status-107

commit 89b051b883
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sun Oct 12 13:18:46 2025 +0700

    feat/encounter-status-107: wip

commit 743c38804a
Merge: d6d60e3 f7b66d2
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sun Oct 12 11:53:03 2025 +0700

    Merge branch 'dev' into feat/encounter-status-107

commit d6d60e38d0
Merge: 9530cdd 18e00bf
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sun Oct 12 11:49:49 2025 +0700

    Merge branch 'dev' into feat/encounter-status-107

commit 18e00bf89a
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Sun Oct 12 11:40:53 2025 +0700

    dev: hotfix, text-size standardization

commit 9530cdd4f9
Merge: 0820cb6 0d1e469
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sun Oct 12 11:41:49 2025 +0700

    Merge branch 'dev' into feat/encounter-status-107

commit 0d1e469ece
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Sun Oct 12 11:40:53 2025 +0700

    dev: hotfix, text-size standardization

commit 0820cb653c
Merge: fff1ce0 867c1b4
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sat Oct 11 00:38:10 2025 +0700

    Merge branch 'dev' into feat/encounter-status-107

commit fff1ce0eb7
Merge: 1a3edd5 3a4b2aa
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sat Oct 11 00:35:16 2025 +0700

    Merge branch 'dev' into feat/encounter-status-107

commit 3a4b2aa6fb
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Sat Oct 11 00:25:44 2025 +0700

    dev: hotfix, moved combobox and datepicker

commit 1a3edd5a1e
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Fri Oct 10 23:58:44 2025 +0700

    dev: hotfix, moved combobox and datepicker

commit f94ccd707b
Merge: 0647675 51d1221
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Wed Oct 8 08:00:01 2025 +0700

    Merge branch 'feat/consultation-82' into fe-prescription-56

commit 06476756fb
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Wed Oct 8 07:58:48 2025 +0700

    fe-prescription-56: wip

commit fdbcfed87f
Merge: 4da896a bd66a88
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Tue Oct 7 03:10:19 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit bd66a8887d
Merge: 19e00fa ba61d05
Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com>
Date:   Tue Oct 7 03:07:46 2025 +0700

    Merge pull request #103 from dikstub-rssa/feat/fe-integrasi-org-src-72

    Feat - Integrasi Org Src

commit ba61d05257
Author: riefive <rie.five@gmail.com>
Date:   Mon Oct 6 12:42:08 2025 +0700

    fix: adjustment division app + flow

commit 8601d4a4fd
Author: riefive <rie.five@gmail.com>
Date:   Mon Oct 6 11:07:05 2025 +0700

    fix: remove shared handlers

commit fff5f2c11d
Author: riefive <rie.five@gmail.com>
Date:   Mon Oct 6 11:06:29 2025 +0700

    fix: update content list of specialist, subspecialist, etc

commit 301cb82803
Author: riefive <rie.five@gmail.com>
Date:   Mon Oct 6 11:00:14 2025 +0700

    fix: update list medicine

commit 3003ec9d80
Author: riefive <rie.five@gmail.com>
Date:   Mon Oct 6 10:45:15 2025 +0700

    fix: update list division + equipment

commit d1bcd6e66c
Author: riefive <rie.five@gmail.com>
Date:   Mon Oct 6 10:38:10 2025 +0700

    fix: update some service

commit 78ae8a8aa0
Author: riefive <rie.five@gmail.com>
Date:   Mon Oct 6 10:26:25 2025 +0700

    fix: medicine method and group

commit 8eaf95dd3e
Author: riefive <rie.five@gmail.com>
Date:   Mon Oct 6 10:20:05 2025 +0700

    fix: update service for unit and uom

commit 58c0dde377
Author: riefive <rie.five@gmail.com>
Date:   Mon Oct 6 10:14:51 2025 +0700

    fix: update handler for unit and uom

commit fe23c75aca
Author: riefive <rie.five@gmail.com>
Date:   Mon Oct 6 10:09:24 2025 +0700

    fix: update some service and handlers

commit 4da896a242
Merge: 285c3ee 19e00fa
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Oct 6 09:55:24 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit ecdc5d80d9
Author: riefive <rie.five@gmail.com>
Date:   Mon Oct 6 09:51:31 2025 +0700

    fix: update device service and handler

commit 45ea70d415
Author: riefive <rie.five@gmail.com>
Date:   Mon Oct 6 09:44:48 2025 +0700

    fix: update crud base

commit 51ddb9d8b5
Merge: 42a54bb 19e00fa
Author: riefive <rie.five@gmail.com>
Date:   Mon Oct 6 09:40:22 2025 +0700

    fix: resolve conflict

commit 19e00fa143
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Mon Oct 6 08:26:08 2025 +0700

    dev: hotfix, moved encounter to pub/component

commit 285c3ee4e5
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Oct 6 07:56:29 2025 +0700

    Merged Stash

commit 421159971e
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Oct 6 04:41:21 2025 +0700

    feat/prescription-56: wip

commit 3a45de413d
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Oct 6 04:31:08 2025 +0700

    Merge from Stash

commit e959c3ae61
Merge: 32c69af ad4695c
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Oct 6 04:21:06 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit ad4695c867
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Mon Oct 6 04:18:55 2025 +0700

    dev: hotfix, encounter content back nav

commit 42a54bbb3b
Merge: a7cbbee 55559a4
Author: riefive <rie.five@gmail.com>
Date:   Sat Oct 4 09:07:03 2025 +0700

    fix: solve conflict after pull

commit a7cbbeeda9
Author: riefive <rie.five@gmail.com>
Date:   Sat Oct 4 09:05:28 2025 +0700

    feat(division): fixing logic treeview

commit 71e0615ee1
Author: riefive <rie.five@gmail.com>
Date:   Sat Oct 4 08:49:38 2025 +0700

    feat(division): restructure division parent

commit f02903e756
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 3 15:00:10 2025 +0700

    feat(division): change parent id to number before integrate

commit 2e8667a780
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 3 14:47:02 2025 +0700

    feat(division): parent id to default null

commit e65e562690
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 3 12:45:05 2025 +0700

    feat(division): change form attribute

commit 9407501c49
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 3 11:03:19 2025 +0700

    feat(division): change compoent combobox to tree select

commit c5ba07a226
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 3 10:54:35 2025 +0700

    feat(division): create tree item converter for division

commit 8e7ce771b0
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 15:31:27 2025 +0700

    test: call division position

commit 7edab33427
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 14:49:55 2025 +0700

    fix: handler reset state

commit ce59eac86c
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 14:37:17 2025 +0700

    fix: list unit

commit 39d2869ffb
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 14:34:19 2025 +0700

    fix: list with params error

commit 3c046a4d82
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 14:12:10 2025 +0700

    fix: list integration

commit 6feb480a51
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 11:16:23 2025 +0700

    fix: change get encounter class to constants

commit d7d984810e
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 11:00:05 2025 +0700

    remove previous list + form from any features

commit a6377ef943
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 15:24:54 2025 +0700

    fix: includes for unit

commit b00b9b198e
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 15:17:31 2025 +0700

    fix: includes for medicine list

commit 4908f16770
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 14:56:11 2025 +0700

    fix: search on list file

commit 41405ae113
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 14:36:48 2025 +0700

    fix: resolve list organization source

commit 6b69e48bd6
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 13:05:36 2025 +0700

    feat(installation): add encounter list

commit 59847dce34
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 13:01:24 2025 +0700

    chore: add shared handlers

commit e78342829e
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 12:38:04 2025 +0700

    feat(installation): integrate api installation

commit 55559a4683
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 3 15:00:10 2025 +0700

    feat(division): change parent id to number before integrate

commit 2d8c751788
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 3 14:47:02 2025 +0700

    feat(division): parent id to default null

commit f374f9ef5b
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 3 12:45:05 2025 +0700

    feat(division): change form attribute

commit 1837afce6c
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 3 11:03:19 2025 +0700

    feat(division): change compoent combobox to tree select

commit 539a1cefb0
Author: riefive <rie.five@gmail.com>
Date:   Fri Oct 3 10:54:35 2025 +0700

    feat(division): create tree item converter for division

commit 32c69af4e1
Merge: 0752855 10bbee9
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Fri Oct 3 06:05:35 2025 +0700

    Merge branch 'feat/layout-cleaning' into fe-prescription-56

commit 757b8c0444
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 15:31:27 2025 +0700

    test: call division position

commit 378e6773b8
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 14:49:55 2025 +0700

    fix: handler reset state

commit 0e115eed5e
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 14:37:17 2025 +0700

    fix: list unit

commit d544d031c3
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 14:34:19 2025 +0700

    fix: list with params error

commit 693d8225bf
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 14:12:10 2025 +0700

    fix: list integration

commit 0752855808
Merge: f83dbfe c0557cc
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Thu Oct 2 12:46:54 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit fc3bda14f4
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 11:16:23 2025 +0700

    fix: change get encounter class to constants

commit 9603915fd7
Author: riefive <rie.five@gmail.com>
Date:   Thu Oct 2 11:00:05 2025 +0700

    remove previous list + form from any features

commit 546423bdfb
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 15:24:54 2025 +0700

    fix: includes for unit

commit db48233f6c
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 15:17:31 2025 +0700

    fix: includes for medicine list

commit 54a5aaa78f
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 14:56:11 2025 +0700

    fix: search on list file

commit cc41118570
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 14:36:48 2025 +0700

    fix: resolve list organization source

commit 6a7a9cda80
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 13:05:36 2025 +0700

    feat(installation): add encounter list

commit c96d738379
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 13:01:24 2025 +0700

    chore: add shared handlers

commit a48f375018
Author: riefive <rie.five@gmail.com>
Date:   Wed Oct 1 12:38:04 2025 +0700

    feat(installation): integrate api installation

commit f83dbfeae3
Merge: ba77ed1 f29eb38
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Oct 1 04:02:05 2025 +0700

    Merge branch 'feat/layout-cleaning' into fe-prescription-56

commit ba77ed1bb5
Merge: 4fbd8ee 97d36f1
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Sep 29 08:27:01 2025 +0700

    Merge branch 'dev' into fe-prescription-56

commit 4fbd8ee757
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sun Sep 28 07:10:32 2025 +0700

    feat/prescription-56: merapikan models
This commit is contained in:
hasyim_kai
2025-11-18 13:26:58 +07:00
parent 270869b928
commit 9d93afed2b
151 changed files with 7702 additions and 270 deletions
@@ -0,0 +1,90 @@
<script setup lang="ts">
import { ActionEvents, type LinkItem, type ListItemDto } from '~/components/pub/my-ui/data/types';
const props = defineProps<{
rec: ListItemDto
}>()
const recId = inject<Ref<number>>('rec_id')!
const recAction = inject<Ref<string>>('rec_action')!
const recItem = inject<Ref<any>>('rec_item')!
const activeKey = ref<string | null>(null)
const linkItems: LinkItem[] = [
{
label: 'Print',
onClick: () => {
print()
},
icon: 'i-lucide-printer',
},
{
label: 'Log History',
onClick: () => {
history()
},
icon: 'i-lucide-logs',
},
{
label: 'Hapus',
onClick: () => {
del()
},
icon: 'i-lucide-trash',
},
]
function print() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showProcess
recItem.value = props.rec
}
function history() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showDetail
recItem.value = props.rec
}
function del() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showConfirmDelete
recItem.value = props.rec
}
</script>
<template>
<div>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white dark:data-[state=open]:bg-slate-800"
>
<Icon
name="i-lucide-chevrons-up-down"
class="ml-auto size-4"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg border border-slate-200 bg-white text-black dark:border-slate-700 dark:bg-slate-800 dark:text-white"
align="end"
>
<DropdownMenuGroup>
<DropdownMenuItem
v-for="item in linkItems"
:key="item.label"
class="hover:bg-gray-100 dark:hover:bg-slate-700"
@click="item.onClick"
@mouseenter="activeKey = item.label"
@mouseleave="activeKey = null"
>
<Icon :name="item.icon ?? ''" />
<span :class="activeKey === item.label ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</template>
@@ -0,0 +1,49 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import { toTypedSchema } from '@vee-validate/zod'
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
import Field from '~/components/pub/my-ui/form/field.vue'
import Label from '~/components/pub/my-ui/form/label.vue'
import Select from '~/components/pub/my-ui/form/select.vue'
import { Form } from '~/components/pub/ui/form'
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
import SelectOriginPolyclinic from '~/components/app/bpjs/control-letter/_common/select-origin-polyclinic.vue'
import SelectDestinationPolyclinic from '~/components/app/bpjs/control-letter/_common/select-destination-polyclinic.vue'
import { cn } from '~/lib/utils'
const props = defineProps()
const items = reactive([
{ id: 1, description: 'Shipped from warehouse', createdAt: new Date(Date.now() - 86400000 * 2) },
{ id: 2, description: 'In transit to distribution center', createdAt: new Date(Date.now() - 86400000) },
{ id: 3, description: 'Out for delivery (Current)', createdAt: new Date() },
])
const itemsCount = computed(() => items.length || 0)
</script>
<template>
<ul :class="cn('pb-5 flex flex-col min-h-[30rem]', '')">
<li v-for="(item, index) in items" :key="item.id" class="flex gap-3 items-start">
<div class="flex flex-col items-center">
<div class="h-5 w-5 rounded-full border-2 border-gray-300 flex items-center justify-center">
<div :class="cn('dark:bg-white border-gray-300 rounded-full p-1.5',
index === 0 ? 'bg-green-500' : 'bg-transparent'
)">
</div>
</div>
<hr v-if="index !== itemsCount - 1" class="h-8 w-0.5 bg-gray-300 dark:bg-gray-300" aria-hidden="true">
</div>
<div class="flex justify-between items-center min-w-96">
<div class="max-w-80">
<time :class="cn('font-medium text-gray-800 dark:text-gray-100', '')">
{{ item?.createdAt.toLocaleDateString('id-ID') }}
</time>
<h1 :class="cn('text-gray-500 dark:text-gray-400', '')">{{ item.description }}</h1>
</div>
</div>
</li>
</ul>
</template>
@@ -0,0 +1,104 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import { Calendar as CalendarIcon, Filter as FilterIcon, Search } from 'lucide-vue-next'
import { ref } from 'vue'
import type { Ref } from 'vue'
import type { DateRange } from 'radix-vue'
import { CalendarDate, DateFormatter, getLocalTimeZone } from '@internationalized/date'
import { cn } from '~/lib/utils'
import * as DE from '~/components/pub/my-ui/doc-entry'
const props = defineProps<{
fieldName?: string
label?: string
placeholder?: string
errors?: FormErrors
class?: string
selectClass?: string
fieldGroupClass?: string
labelClass?: string
isRequired?: boolean
}>()
const {
fieldName = 'job',
label = 'Pekerjaan',
placeholder = 'Pilih pekerjaan',
errors,
class: containerClass,
fieldGroupClass,
labelClass,
} = props
const dateRange = ref<{ from: Date | null; to: Date | null }>({
from: new Date(),
to: new Date(),
})
const df = new DateFormatter('en-US', {
dateStyle: 'medium',
})
const value = ref({
start: new CalendarDate(2022, 1, 20),
end: new CalendarDate(2022, 1, 20).add({ days: 20 }),
}) as Ref<DateRange>
</script>
<template>
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
<DE.Label
:label-for="fieldName"
:class="cn('select-field-label', labelClass)"
:is-required="isRequired"
>
{{ label }}
</DE.Label>
<DE.Field
:id="fieldName"
:errors="errors"
:class="cn('select-field-wrapper')"
>
<FormField
v-slot="{ componentField }"
:name="fieldName"
>
<FormItem>
<FormControl>
<Popover>
<PopoverTrigger as-child>
<Button
variant="outline"
:class="cn('w-full bg-white border-gray-400 justify-start text-left font-normal', !value && 'text-muted-foreground')"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<template v-if="value.start">
<template v-if="value.end">
{{ df.format(value.start.toDate(getLocalTimeZone())) }} -
{{ df.format(value.end.toDate(getLocalTimeZone())) }}
</template>
<template v-else>
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
</template>
</template>
<template v-else> Pick a date </template>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<RangeCalendar
v-model="value"
initial-focus
:number-of-months="2"
@update:start-value="(startDate) => (value.start = startDate)"
/>
</PopoverContent>
</Popover>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</DE.Field>
</DE.Cell>
</template>
@@ -0,0 +1,70 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
import { cn, mapToComboboxOptList } from '~/lib/utils'
import { occupationCodes } from '~/lib/constants'
import * as DE from '~/components/pub/my-ui/doc-entry'
const props = defineProps<{
fieldName?: string
label?: string
placeholder?: string
errors?: FormErrors
class?: string
selectClass?: string
fieldGroupClass?: string
labelClass?: string
isRequired?: boolean
}>()
const {
fieldName = 'job',
label = 'Pekerjaan',
placeholder = 'Pilih pekerjaan',
errors,
class: containerClass,
fieldGroupClass,
labelClass,
} = props
// Generate job options from constants, sama seperti pola genderCodes
const jobOptions = mapToComboboxOptList(occupationCodes)
</script>
<template>
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
<DE.Label
:label-for="fieldName"
:class="cn('select-field-label', labelClass)"
:is-required="isRequired"
>
{{ label }}
</DE.Label>
<DE.Field
:id="fieldName"
:errors="errors"
:class="cn('select-field-wrapper')"
>
<FormField
v-slot="{ componentField }"
:name="fieldName"
>
<FormItem>
<FormControl>
<Combobox
class="focus:ring-0 focus:ring-offset-0"
:id="fieldName"
v-bind="componentField"
:items="jobOptions"
:placeholder="placeholder"
search-placeholder="Cari..."
empty-message="Data tidak ditemukan"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</DE.Field>
</DE.Cell>
</template>
@@ -0,0 +1,70 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
import { cn, mapToComboboxOptList } from '~/lib/utils'
import { occupationCodes } from '~/lib/constants'
import * as DE from '~/components/pub/my-ui/doc-entry'
const props = defineProps<{
fieldName?: string
label?: string
placeholder?: string
errors?: FormErrors
class?: string
selectClass?: string
fieldGroupClass?: string
labelClass?: string
isRequired?: boolean
}>()
const {
fieldName = 'job',
label = 'Pekerjaan',
placeholder = 'Pilih pekerjaan',
errors,
class: containerClass,
fieldGroupClass,
labelClass,
} = props
// Generate job options from constants, sama seperti pola genderCodes
const jobOptions = mapToComboboxOptList(occupationCodes)
</script>
<template>
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
<DE.Label
:label-for="fieldName"
:class="cn('select-field-label', labelClass)"
:is-required="isRequired"
>
{{ label }}
</DE.Label>
<DE.Field
:id="fieldName"
:errors="errors"
:class="cn('select-field-wrapper')"
>
<FormField
v-slot="{ componentField }"
:name="fieldName"
>
<FormItem>
<FormControl>
<Combobox
class="focus:ring-0 focus:ring-offset-0"
:id="fieldName"
v-bind="componentField"
:items="jobOptions"
:placeholder="placeholder"
search-placeholder="Cari..."
empty-message="Data tidak ditemukan"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</DE.Field>
</DE.Cell>
</template>
@@ -0,0 +1,128 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import { toTypedSchema } from '@vee-validate/zod'
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
import Field from '~/components/pub/my-ui/form/field.vue'
import Label from '~/components/pub/my-ui/form/label.vue'
import Select from '~/components/pub/my-ui/form/select.vue'
import { Form } from '~/components/pub/ui/form'
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
import SelectOriginPolyclinic from '~/components/app/bpjs/control-letter/_common/select-origin-polyclinic.vue'
import SelectDestinationPolyclinic from '~/components/app/bpjs/control-letter/_common/select-destination-polyclinic.vue'
import { cn } from '~/lib/utils'
import SelectDateRange from './_common/select-date-range.vue'
interface InstallationFormData {
name: string
code: string
encounterClassCode: string
}
const props = defineProps<{
installation: {
msg: {
placeholder: string
}
items: {
value: string
label: string
code: string
}[]
}
schema: any
initialValues?: Partial<InstallationFormData>
errors?: FormErrors
}>()
const emit = defineEmits<{
submit: [values: InstallationFormData, resetForm: () => void]
reset: [resetForm: () => void]
}>()
const formSchema = toTypedSchema(props.schema)
// Form submission handler
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
const formData: InstallationFormData = {
name: values.name || '',
code: values.code || '',
encounterClassCode: values.encounterClassCode || '',
}
emit('submit', formData, resetForm)
}
// Form cancel handler
function onResetForm({ resetForm }: { resetForm: () => void }) {
emit('reset', resetForm)
}
const items = ref([
{ label: 'Rujukan Internal', value: 'ri' },
{ label: 'SEP Rujukan', value: 'sr' },
])
</script>
<template>
<Form
v-slot="{ handleSubmit, resetForm }"
as=""
keep-values
:validation-schema="formSchema"
:initial-values="initialValues"
>
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
<div class="mb-5 border-b border-b-slate-300 pb-7 text-lg xl:text-xl">
<div class="flex flex-col justify-between">
<SelectDateRange
field-name="releaseDate"
label="Tanggal Penerbitan"
placeholder="Tanggal Penerbitan"
:errors="errors"
is-required
/>
<SelectDateRange
field-name="controlPlanDate"
label="Tanggal Rencana Kontrol"
placeholder="Tanggal Rencana Kontrol"
:errors="errors"
is-required
/>
<InputBase
field-name="patientName"
label="Nama Pasien"
placeholder="Nama Pasien"
/>
<InputBase
field-name="cardNumber"
label="Nomor Kartu"
placeholder="Nomor Kartu"
/>
<InputBase
field-name="sepNumber"
label="Nomor SEP"
placeholder="Nomor SEP"
/>
<SelectOriginPolyclinic
field-name="originPolyclinic"
label="Poliklinik Asal"
placeholder="Pilih Poliklinik Asal"
:errors="errors"
is-required
/>
<SelectDestinationPolyclinic
field-name="destinationPolyclinic"
label="Poliklinik Tujuan"
placeholder="Pilih Poliklinik Tujuan"
:errors="errors"
is-required
/>
</div>
</div>
<div class="my-2 flex items-center gap-3 justify-end">
<Button @click="onResetForm" variant="secondary">Reset</Button>
<Button @click="onSubmitForm">Terapkan</Button>
</div>
</form>
</Form>
</template>
@@ -0,0 +1,108 @@
import type { Config } from '~/components/pub/my-ui/data-table'
import type { Patient } from '~/models/patient'
import { defineAsyncComponent } from 'vue'
import { educationCodes, genderCodes } from '~/lib/constants'
import { calculateAge } from '~/lib/utils'
const action = defineAsyncComponent(() => import('./_common/dropdown-action.vue'))
const statusBadge = defineAsyncComponent(() => import('~/components/pub/my-ui/badge/status-badge.vue'))
export const config: Config = {
cols: [{}, {}, {}, {},{}, {}, {}, {}, {}, {width: 90},{width: 10},],
headers: [
[
{ label: 'No Surat' },
{ label: 'No MR' },
{ label: 'Nama' },
{ label: 'Tgl Rencana Kontrol' },
{ label: 'Tgl Penerbitan' },
{ label: 'Klinik Asal' },
{ label: 'Klinik Tujuan' },
{ label: 'DPJP' },
{ label: 'No SEP Asal' },
{ label: 'Status' },
{ label: 'Action' },
],
],
keys: ['birth_date', 'number', 'person.name', 'birth_date', 'birth_date',
'birth_date', 'number', 'person.name', 'birth_date', 'status', 'action'],
delKeyNames: [
{ key: 'code', label: 'Kode' },
{ key: 'name', label: 'Nama' },
],
parses: {
patientId: (rec: unknown): unknown => {
const patient = rec as Patient
return patient.number
},
identity_number: (rec: unknown): unknown => {
const { person } = rec as Patient
if (person.nationality == 'WNA') {
return person.passportNumber
}
return person.residentIdentityNumber || '-'
},
birth_date: (rec: unknown): unknown => {
const { person } = rec as Patient
if (typeof person.birthDate == 'object' && person.birthDate) {
return (person.birthDate as Date).toLocaleDateString('id-ID')
} else if (typeof person.birthDate == 'string') {
return (person.birthDate as string).substring(0, 10)
}
return person.birthDate
},
patient_age: (rec: unknown): unknown => {
const { person } = rec as Patient
return calculateAge(person.birthDate)
},
gender: (rec: unknown): unknown => {
const { person } = rec as Patient
if (typeof person.gender_code == 'number' && person.gender_code >= 0) {
return person.gender_code
} else if (typeof person.gender_code === 'string' && person.gender_code) {
return genderCodes[person.gender_code] || '-'
}
return '-'
},
education: (rec: unknown): unknown => {
const { person } = rec as Patient
if (typeof person.education_code == 'number' && person.education_code >= 0) {
return person.education_code
} else if (typeof person.education_code === 'string' && person.education_code) {
return educationCodes[person.education_code] || '-'
}
return '-'
},
},
components: {
action(rec, idx) {
return {
idx,
rec: rec as object,
component: action,
}
},
status(rec, idx) {
return {
idx,
rec: rec as object,
component: statusBadge,
}
},
},
htmls: {
patient_address(_rec) {
return '-'
},
},
}
@@ -0,0 +1,31 @@
<script setup lang="ts">
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
import { config } from './list.cfg'
interface Props {
data: any[]
paginationMeta: PaginationMeta
}
defineProps<Props>()
const emit = defineEmits<{
pageChange: [page: number]
}>()
function handlePageChange(page: number) {
emit('pageChange', page)
}
</script>
<template>
<div class="space-y-4">
<PubMyUiDataTable
v-bind="config"
:rows="data"
:skeleton-size="paginationMeta?.pageSize"
/>
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
</div>
</template>
@@ -0,0 +1,30 @@
<script setup lang="ts">
import Button from '~/components/pub/ui/button/Button.vue'
const props = defineProps<{
rec: any
idx?: number
}>()
// Try to get proses handler from parent via inject
const prosesHandler = inject<(rec: any) => void>('proses-handler', null)
function handleProses() {
if (prosesHandler) {
prosesHandler(props.rec)
}
}
</script>
<template>
<div class="flex justify-center">
<Button
type="button"
class="border-orange-500 bg-orange-500 text-white hover:bg-orange-600"
@click="handleProses"
>
Proses
</Button>
</div>
</template>
@@ -0,0 +1,164 @@
<script setup lang="ts">
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '~/components/pub/ui/table'
import Input from '~/components/pub/ui/input/Input.vue'
import DatepickerSingle from '~/components/pub/my-ui/datepicker/datepicker-single.vue'
import Button from '~/components/pub/ui/button/Button.vue'
import { format, parseISO } from 'date-fns'
import { id as localeID } from 'date-fns/locale'
interface Props {
open?: boolean
tanggalPemeriksaan?: string | Date
jadwalTanggalPemeriksaan?: string | Date
isLoading?: boolean
}
const props = withDefaults(defineProps<Props>(), {
open: false,
tanggalPemeriksaan: undefined,
jadwalTanggalPemeriksaan: undefined,
isLoading: false,
})
const emit = defineEmits<{
'update:open': [value: boolean]
'update:date': [value: string | undefined]
'update:schedule': [value: string | undefined]
submit: [data: { tanggalPemeriksaan: string | undefined; jadwalTanggalPemeriksaan: string | undefined }]
}>()
// Local state for jadwal tanggal pemeriksaan
const jadwalTanggal = ref<string | undefined>(
props.jadwalTanggalPemeriksaan
? typeof props.jadwalTanggalPemeriksaan === 'string'
? props.jadwalTanggalPemeriksaan
: format(props.jadwalTanggalPemeriksaan, 'yyyy-MM-dd')
: undefined,
)
// Watch for external changes
watch(
() => props.jadwalTanggalPemeriksaan,
(newValue) => {
if (newValue) {
jadwalTanggal.value =
typeof newValue === 'string' ? newValue : format(newValue, 'yyyy-MM-dd')
} else {
jadwalTanggal.value = undefined
}
},
)
// Watch local changes
watch(jadwalTanggal, (newValue) => {
emit('update:schedule', newValue)
})
// Format date for display
const formattedTanggalPemeriksaan = computed(() => {
if (!props.tanggalPemeriksaan) return ''
try {
const date =
props.tanggalPemeriksaan instanceof Date
? props.tanggalPemeriksaan
: parseISO(props.tanggalPemeriksaan)
return format(date, 'dd MMMM yyyy', { locale: localeID })
} catch {
return props.tanggalPemeriksaan.toString()
}
})
// Handle submit
function handleSubmit() {
emit('submit', {
tanggalPemeriksaan: props.tanggalPemeriksaan
? typeof props.tanggalPemeriksaan === 'string'
? props.tanggalPemeriksaan
: format(props.tanggalPemeriksaan, 'yyyy-MM-dd')
: undefined,
jadwalTanggalPemeriksaan: jadwalTanggal.value,
})
}
// Table data for Jadwal Ruang Tindakan
const scheduleData = [
{
no: 1,
namaJenis: 'Ruang Tindakan',
jenisPemeriksaan: 'KEMOTERAPI',
},
]
</script>
<template>
<Dialog
:open="open"
size="lg"
title="Verifikasi Jadwal Pasien"
@update:open="$emit('update:open', $event)"
>
<div class="space-y-6 py-4">
<!-- Jadwal Ruang Tindakan Section -->
<div class="space-y-3">
<h4 class="text-base font-semibold">Jadwal Ruang Tindakan</h4>
<div class="overflow-hidden rounded-md border">
<Table>
<TableHeader class="bg-gray-50">
<TableRow>
<TableHead class="font-semibold">NO</TableHead>
<TableHead class="font-semibold">NAMA JENIS</TableHead>
<TableHead class="font-semibold">JENIS PEMERIKSAAN</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-for="(row, index) in scheduleData" :key="index">
<TableCell>{{ row.no }}</TableCell>
<TableCell>{{ row.namaJenis }}</TableCell>
<TableCell>{{ row.jenisPemeriksaan }}</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</div>
<!-- Tanggal Pemeriksaan Section -->
<div class="space-y-2">
<label class="text-sm font-medium">Tanggal Pemeriksaan</label>
<Input
:model-value="formattedTanggalPemeriksaan"
:disabled="true"
class="bg-gray-100 text-gray-700"
readonly
/>
</div>
<!-- Jadwal Tanggal Pemeriksaan Section -->
<div class="space-y-2">
<label class="text-sm font-medium">
Jadwal Tanggal Pemeriksaan
<span class="text-red-500">*</span>
</label>
<DatepickerSingle
v-model="jadwalTanggal"
placeholder="Pilih Jadwal"
:disabled="isLoading"
/>
</div>
<!-- Action Button -->
<div class="flex justify-end pt-4">
<Button
type="button"
:disabled="isLoading || !jadwalTanggal"
class="bg-gradient-to-r from-orange-500 to-orange-400 hover:from-orange-600 hover:to-orange-500 text-white shadow-md"
@click="handleSubmit"
>
<Icon name="i-lucide-calendar-check" class="mr-2 h-4 w-4" />
Simpan Jadwal
</Button>
</div>
</div>
</Dialog>
</template>
@@ -0,0 +1,63 @@
<script setup lang="ts">
import type { LinkItem, ListItemDto } from '~/components/pub/my-ui/data/types'
const props = defineProps<{
rec: ListItemDto
}>()
const recId = inject<Ref<number>>('rec_id')!
const recAction = inject<Ref<string>>('rec_action')!
const recItem = inject<Ref<any>>('rec_item')!
const activeKey = ref<string | null>(null)
const linkItems: LinkItem[] = [
{
label: 'Proses',
onClick: () => {
process()
},
icon: 'i-lucide-pencil',
},
]
function process() {
recId.value = props.rec.id || 0
recAction.value = 'Process'
recItem.value = props.rec
}
</script>
<template>
<div>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white dark:data-[state=open]:bg-slate-800"
>
<Icon
name="i-lucide-chevrons-up-down"
class="ml-auto size-4"
/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg border border-slate-200 bg-white text-black dark:border-slate-700 dark:bg-slate-800 dark:text-white"
align="end"
>
<DropdownMenuGroup>
<DropdownMenuItem
v-for="item in linkItems"
:key="item.label"
class="hover:bg-gray-100 dark:hover:bg-slate-700"
@click="item.onClick"
@mouseenter="activeKey = item.label"
@mouseleave="activeKey = null"
>
<Icon :name="item.icon ?? ''" />
<span :class="activeKey === item.label ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</template>
@@ -0,0 +1,302 @@
<script setup lang="ts">
// Components
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
import Input from '~/components/pub/ui/input/Input.vue'
import Button from '~/components/pub/ui/button/Button.vue'
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
import DatepickerSingle from '~/components/pub/my-ui/datepicker/datepicker-single.vue'
// Helpers
import type z from 'zod'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { chemotherapySchema } from "~/schemas/chemotherapy.schema"
interface Props {
values?: any
isLoading?: boolean
isReadonly?: boolean
}
const props = defineProps<Props>()
const isLoading = props.isLoading !== undefined ? props.isLoading : false
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
const items = [
{ value: 'item-1', label: 'Item 1' },
{ value: 'item-2', label: 'Item 2' },
{ value: 'item-3', label: 'Item 3' },
]
const emit = defineEmits<{
submit: [values: any, resetForm: () => void]
cancel: [resetForm: () => void]
}>()
const { defineField, errors, meta } = useForm({
validationSchema: toTypedSchema(chemotherapySchema),
initialValues: {
namaPasien: '',
tanggalLahir: '',
noRM: '',
alamat: '',
beratBadan: '',
tinggiBadan: '',
diagnosa: '',
siklus: '',
periodeAwal: '',
periodeAkhir: '',
tanggalKemoterapi: '',
dokterKRJ: '',
},
})
// Define form fields
const [namaPasien, namaPasienAttrs] = defineField('namaPasien')
const [tanggalLahir, tanggalLahirAttrs] = defineField('tanggalLahir')
const [noRM, noRMAttrs] = defineField('noRM')
const [alamat, alamatAttrs] = defineField('alamat')
const [beratBadan, beratBadanAttrs] = defineField('beratBadan')
const [tinggiBadan, tinggiBadanAttrs] = defineField('tinggiBadan')
const [diagnosa, diagnosaAttrs] = defineField('diagnosa')
const [siklus, siklusAttrs] = defineField('siklus')
const [periodeAwal, periodeAwalAttrs] = defineField('periodeAwal')
const [periodeAkhir, periodeAkhirAttrs] = defineField('periodeAkhir')
const [tanggalKemoterapi, tanggalKemoterapiAttrs] = defineField('tanggalKemoterapi')
const [dokterKRJ, dokterKRJAttrs] = defineField('dokterKRJ')
// Set initial values if provided
if (props.values) {
// Object.entries(props.values).forEach(([key, value]) => {
// if (value !== undefined) {
// const field = defineField(key)[0]
// field.value = value
// }
// })
}
const resetForm = () => {
// Object.keys(meta.value.initialValues).forEach((key) => {
// const field = defineField(key)[0]
// field.value = ''
// })
}
function onSubmitForm() {
const formData = {
namaPasien: namaPasien.value,
tanggalLahir: tanggalLahir.value,
noRM: noRM.value,
alamat: alamat.value,
beratBadan: beratBadan.value,
tinggiBadan: tinggiBadan.value,
diagnosa: diagnosa.value,
siklus: siklus.value,
periodeAwal: periodeAwal.value,
periodeAkhir: periodeAkhir.value,
tanggalKemoterapi: tanggalKemoterapi.value,
dokterKRJ: dokterKRJ.value,
}
emit('submit', formData, resetForm)
}
function onCancelForm() {
emit('cancel', resetForm)
}
</script>
<template>
<form @submit.prevent>
<!-- Data Pasien Section -->
<div class="mb-6">
<h3 class="mb-4 text-lg font-semibold">Data Pasien</h3>
<Block
labelSize="thin"
class="!mb-2.5 !pt-0 xl:!mb-3"
>
<Cell>
<Label height="compact">Nama Pasien</Label>
<Field :errMessage="errors.namaPasien">
<Input
v-model="namaPasien"
v-bind="namaPasienAttrs"
:disabled="isLoading || isReadonly"
placeholder="Masukkan nama pasien"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">Tanggal Lahir</Label>
<Field :errMessage="errors.tanggalLahir">
<DatePicker
v-model="tanggalLahir"
v-bind="tanggalLahirAttrs"
:disabled="isLoading || isReadonly"
placeholder="Pilih tanggal lahir"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">No. RM</Label>
<Field :errMessage="errors.noRM">
<Input
v-model="noRM"
v-bind="noRMAttrs"
:disabled="isLoading || isReadonly"
placeholder="Masukkan nomor RM"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">Alamat</Label>
<Field :errMessage="errors.alamat">
<Input
v-model="alamat"
v-bind="alamatAttrs"
:disabled="isLoading || isReadonly"
placeholder="Masukkan alamat"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">Berat Badan</Label>
<Field :errMessage="errors.beratBadan">
<Input
v-model="beratBadan"
v-bind="beratBadanAttrs"
:disabled="isLoading || isReadonly"
placeholder="Masukkan berat badan"
type="number"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">Tinggi Badan</Label>
<Field :errMessage="errors.tinggiBadan">
<Input
v-model="tinggiBadan"
v-bind="tinggiBadanAttrs"
:disabled="isLoading || isReadonly"
placeholder="Masukkan tinggi badan"
type="number"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">Diagnosa</Label>
<Field :errMessage="errors.diagnosa">
<Combobox
id="diagnose"
v-model="diagnosa"
v-bind="diagnosaAttrs"
:items="items"
:is-disabled="isLoading || isReadonly"
placeholder="Tentukan diagnosa pasien"
search-placeholder="Cari diagnosa"
empty-message="Diagnosa tidak ditemukan"
/>
</Field>
</Cell>
</Block>
</div>
<!-- Protokol Kemoterapi Section -->
<div class="mb-6">
<h3 class="mb-4 text-lg font-semibold">Protokol Kemoterapi</h3>
<Block
labelSize="thin"
class="!mb-2.5 !pt-0 xl:!mb-3"
>
<Cell>
<Label height="compact">Siklus</Label>
<Field :errMessage="errors.siklus">
<Input
v-model="siklus"
v-bind="siklusAttrs"
:disabled="isLoading || isReadonly"
placeholder="Masukkan siklus"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">Periode</Label>
<div class="flex items-center gap-4">
<Field
:errMessage="errors.periodeAwal"
class="flex-1"
>
<DatepickerSingle
v-model="periodeAwal"
v-bind="periodeAwalAttrs"
:disabled="isLoading || isReadonly"
placeholder="Mulai Periode"
/>
</Field>
<span>Sampai</span>
<Field
:errMessage="errors.periodeAkhir"
class="flex-1"
>
<DatepickerSingle
v-model="periodeAkhir"
v-bind="periodeAkhirAttrs"
:disabled="isLoading || isReadonly"
placeholder="Akhir Periode"
/>
</Field>
</div>
</Cell>
<Cell>
<Label height="compact">Tanggal Kemoterapi</Label>
<Field :errMessage="errors.tanggalKemoterapi">
<DatepickerSingle
v-model="tanggalKemoterapi"
v-bind="tanggalKemoterapiAttrs"
:disabled="isLoading || isReadonly"
placeholder="Pilih tanggal kemoterapi"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">Dokter Ruang Tindakan</Label>
<Field :errMessage="errors.dokterKRJ">
<Combobox
id="doctor"
v-model="dokterKRJ"
v-bind="dokterKRJAttrs"
:items="items"
:is-disabled="isLoading || isReadonly"
placeholder="Pilih dokter"
search-placeholder="Cari dokter"
empty-message="Dokter tidak ditemukan"
/>
</Field>
</Cell>
</Block>
</div>
<!-- Form Actions -->
<div class="flex justify-end gap-2 py-2">
<Button
type="button"
variant="secondary"
class="w-[120px]"
@click="onCancelForm"
>
Kembali
</Button>
<Button
v-if="!isReadonly"
type="button"
class="w-[120px]"
:disabled="isLoading || !meta.valid"
@click="onSubmitForm"
>
Simpan
</Button>
</div>
</form>
</template>
@@ -0,0 +1,37 @@
<script setup lang="ts">
// Components
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
// Types
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
// Configs
import { config } from './list-cfg.admin'
interface Props {
data: any[]
paginationMeta: PaginationMeta
}
defineProps<Props>()
const emit = defineEmits<{
pageChange: [page: number]
}>()
function handlePageChange(page: number) {
emit('pageChange', page)
}
</script>
<template>
<div class="space-y-4">
<PubMyUiDataTable
v-bind="config"
:rows="data"
:skeleton-size="paginationMeta?.pageSize"
/>
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
</div>
</template>
@@ -0,0 +1,80 @@
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
import { defineAsyncComponent } from 'vue'
type SmallDetailDto = any
const action = defineAsyncComponent(() => import('./dropdown-action-process.vue'))
export const config: Config = {
cols: [
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 50 },
],
headers: [
[
{ label: 'TANGGAL' },
{ label: 'NO. RM' },
{ label: 'NO. BILL' },
{ label: 'JK' },
{ label: 'ALAMAT' },
{ label: 'KLINIK ASAL' },
{ label: 'NAMA DOKTER' },
{ label: 'CARA BAYAR' },
{ label: 'RUJUKAN' },
{ label: 'KET. RUJUKAN' },
{ label: 'ASAL' },
{ label: '' },
],
],
keys: [
'tanggal',
'noRm',
'noBill',
'jk',
'alamat',
'klinik',
'dokter',
'caraBayar',
'rujukan',
'ketRujukan',
'asal',
'action',
],
delKeyNames: [
{ key: 'code', label: 'Kode' },
{ key: 'name', label: 'Nama' },
],
parses: {
parent: (rec: unknown): unknown => {
const recX = rec as SmallDetailDto
return recX.parent?.name || '-'
},
},
components: {
action(rec, idx) {
const res: RecComponent = {
idx,
rec: rec as object,
component: action,
}
return res
},
},
htmls: {},
}
@@ -0,0 +1,69 @@
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
import { defineAsyncComponent } from 'vue'
type SmallDetailDto = any
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
export const config: Config = {
cols: [
{ width: 60 },
{ width: 200 },
{ width: 100 },
{ width: 100 },
{ width: 150 },
{ width: 80 },
{ width: 200 },
{ width: 120 },
],
headers: [
[
{ label: 'NO.' },
{ label: 'NAMA OBAT' },
{ label: 'DOSIS' },
{ label: 'SATUAN' },
{ label: 'RUTE PEMBERIAN' },
{ label: 'HARI' },
{ label: 'CATATAN' },
{ label: '' },
],
],
keys: [
'number',
'namaObat',
'dosis',
'satuan',
'rute',
'hari',
'catatan',
'action',
],
delKeyNames: [
{ key: 'code', label: 'Kode' },
{ key: 'name', label: 'Nama' },
],
parses: {
parent: (rec: unknown): unknown => {
const recX = rec as SmallDetailDto
return recX.parent?.name || '-'
},
},
components: {
action(rec, idx) {
const res: RecComponent = {
idx,
rec: rec as object,
component: action,
}
return res
},
},
htmls: {},
}
@@ -0,0 +1,62 @@
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
import { defineAsyncComponent } from 'vue'
type SmallDetailDto = any
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
export const config: Config = {
cols: [
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 50 },
],
headers: [
[
{ label: 'NO.' },
{ label: 'TANGGAL' },
{ label: 'SIKLUS' },
{ label: 'PERIODE KEMOTERAPI' },
{ label: 'KEHADIRAN' },
{ label: '' },
],
],
keys: [
'number',
'tanggal',
'siklus',
'periode',
'kehadiran',
'action',
],
delKeyNames: [
{ key: 'code', label: 'Kode' },
{ key: 'name', label: 'Nama' },
],
parses: {
parent: (rec: unknown): unknown => {
const recX = rec as SmallDetailDto
return recX.parent?.name || '-'
},
},
components: {
action(rec, idx) {
const res: RecComponent = {
idx,
rec: rec as object,
component: action,
}
return res
},
},
htmls: {},
}
@@ -0,0 +1,80 @@
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
import { defineAsyncComponent } from 'vue'
type SmallDetailDto = any
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
export const config: Config = {
cols: [
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 120 },
{ width: 50 },
],
headers: [
[
{ label: 'TANGGAL' },
{ label: 'NO. RM' },
{ label: 'NO. BILL' },
{ label: 'JK' },
{ label: 'ALAMAT' },
{ label: 'KLINIK ASAL' },
{ label: 'NAMA DOKTER' },
{ label: 'CARA BAYAR' },
{ label: 'RUJUKAN' },
{ label: 'KET. RUJUKAN' },
{ label: 'ASAL' },
{ label: '' },
],
],
keys: [
'tanggal',
'noRm',
'noBill',
'jk',
'alamat',
'klinik',
'dokter',
'caraBayar',
'rujukan',
'ketRujukan',
'asal',
'action',
],
delKeyNames: [
{ key: 'code', label: 'Kode' },
{ key: 'name', label: 'Nama' },
],
parses: {
parent: (rec: unknown): unknown => {
const recX = rec as SmallDetailDto
return recX.parent?.name || '-'
},
},
components: {
action(rec, idx) {
const res: RecComponent = {
idx,
rec: rec as object,
component: action,
}
return res
},
},
htmls: {},
}
@@ -0,0 +1,78 @@
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
import { defineAsyncComponent } from 'vue'
type SmallDetailDto = any
const statusBadge = defineAsyncComponent(() => import('./status-badge.vue'))
const verifyButton = defineAsyncComponent(() => import('./verify-button.vue'))
export const config: Config = {
cols: [
{ width: 120 },
{ width: 150 },
{ width: 150 },
{ width: 150 },
{ width: 150 },
{ width: 180 },
{ width: 150 },
{ width: 100 },
],
headers: [
[
{ label: 'TANGGAL MASUK' },
{ label: 'PJ BERKAS RM' },
{ label: 'DOKTER' },
{ label: 'JENIS RUANGAN' },
{ label: 'JENIS TINDAKAN' },
{ label: 'TANGGAL JADWAL TINDAKAN' },
{ label: 'STATUS' },
{ label: 'AKSI' },
],
],
keys: [
'tanggalMasuk',
'pjBerkasRm',
'dokter',
'jenisRuangan',
'jenisTindakan',
'tanggalJadwalTindakan',
'status',
'action',
],
delKeyNames: [
{ key: 'code', label: 'Kode' },
{ key: 'name', label: 'Nama' },
],
parses: {
parent: (rec: unknown): unknown => {
const recX = rec as SmallDetailDto
return recX.parent?.name || '-'
},
},
components: {
status(rec, idx) {
const res: RecComponent = {
idx,
rec: rec as object,
component: statusBadge,
}
return res
},
action(rec, idx) {
const res: RecComponent = {
idx,
rec: rec as object,
component: verifyButton,
}
return res
},
},
htmls: {},
}
@@ -0,0 +1,99 @@
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
import { defineAsyncComponent } from 'vue'
import { format, parseISO } from 'date-fns'
import { id as localeID } from 'date-fns/locale'
type VisitDto = any
const statusBadge = defineAsyncComponent(() => import('./status-badge.vue'))
const verifyButton = defineAsyncComponent(() => import('./verify-button.vue'))
export const config: Config = {
cols: [
{ width: 150 }, // TANGGAL MASUK
{ width: 180 }, // PJ BERKAS RM
{ width: 200 }, // DOKTER
{ width: 150 }, // JENIS RUANGAN
{ width: 150 }, // JENIS TINDAKAN
{ width: 180 }, // TANGGAL JADWAL TINDAKAN
{ width: 150 }, // STATUS
{ width: 120 }, // AKSI
],
headers: [
[
{ label: 'TANGGAL MASUK' },
{ label: 'PJ BERKAS RM' },
{ label: 'DOKTER' },
{ label: 'JENIS RUANGAN' },
{ label: 'JENIS TINDAKAN' },
{ label: 'TANGGAL JADWAL TINDAKAN' },
{ label: 'STATUS' },
{ label: 'AKSI' },
],
],
keys: [
'tanggal_masuk',
'pj_berkas_rm',
'dokter',
'jenis_ruangan',
'jenis_tindakan',
'tanggal_jadwal_tindakan',
'status',
'action',
],
delKeyNames: [
{ key: 'id', label: 'ID' },
{ key: 'nama', label: 'Nama' },
],
parses: {
tanggal_masuk: (rec: unknown): string => {
const recX = rec as VisitDto
if (!recX.tanggal_masuk) return '-'
try {
const date = typeof recX.tanggal_masuk === 'string' ? parseISO(recX.tanggal_masuk) : recX.tanggal_masuk
return format(date, 'dd MMMM yyyy', { locale: localeID })
} catch {
return recX.tanggal_masuk.toString()
}
},
tanggal_jadwal_tindakan: (rec: unknown): string => {
const recX = rec as VisitDto
if (!recX.tanggal_jadwal_tindakan) return '-'
try {
const date =
typeof recX.tanggal_jadwal_tindakan === 'string'
? parseISO(recX.tanggal_jadwal_tindakan)
: recX.tanggal_jadwal_tindakan
return format(date, 'dd MMMM yyyy', { locale: localeID })
} catch {
return recX.tanggal_jadwal_tindakan.toString()
}
},
},
components: {
status(rec, idx) {
const res: RecComponent = {
idx,
rec: rec as object,
component: statusBadge,
}
return res
},
action(rec, idx) {
const res: RecComponent = {
idx,
rec: rec as object,
component: verifyButton,
}
return res
},
},
htmls: {},
}
@@ -0,0 +1,37 @@
<script setup lang="ts">
// Components
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
// Types
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
// Configs
import { config } from './list-cfg.verification'
interface Props {
data: any[]
paginationMeta: PaginationMeta
}
defineProps<Props>()
const emit = defineEmits<{
pageChange: [page: number]
}>()
function handlePageChange(page: number) {
emit('pageChange', page)
}
</script>
<template>
<div class="space-y-4">
<PubMyUiDataTable
v-bind="config"
:rows="data"
:skeleton-size="paginationMeta?.pageSize"
/>
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
</div>
</template>
@@ -0,0 +1,76 @@
<script setup lang="ts">
// Components
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
// Types
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
// Configs
import { config } from './list-cfg.medicine'
const searchQuery = ref('')
function handleSearch(event: Event) {
const target = event.target as HTMLInputElement
searchQuery.value = target.value
// TODO: Implement search logic here
// You can emit an event to parent or filter data directly
}
interface Props {
data: any[]
paginationMeta: PaginationMeta
}
defineProps<Props>()
const emit = defineEmits<{
pageChange: [page: number]
}>()
function handlePageChange(page: number) {
emit('pageChange', page)
}
</script>
<template>
<div class="space-y-4">
<!-- Title and Search Section -->
<div class="flex flex-col items-start">
<div class="flex items-center justify-between w-full">
<div>
<h2 class="mb-1 text-xl font-semibold">Protokol Obat Kemoterapi</h2>
<p class="mb-4 text-sm text-gray-500">Daftar obat-obatan yang digunakan dalam protokol kemoterapi.</p>
</div>
<button class="rounded bg-orange-500 px-3 py-2 text-white hover:bg-orange-600">
<i class="ri-add-line"></i>
Tambah Obat Kemoterapi
</button>
</div>
<div class="relative mt-10 w-72">
<input
v-model="searchQuery"
type="text"
placeholder="Cari obat..."
class="w-full rounded-md border px-4 py-2 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
@input="handleSearch"
/>
<span class="absolute right-3 top-2.5 text-gray-400">
<i class="ri-search-line"></i>
</span>
</div>
</div>
<PubMyUiDataTable
v-bind="config"
:rows="data"
:skeleton-size="paginationMeta?.pageSize"
/>
<PaginationView
:pagination-meta="paginationMeta"
@page-change="handlePageChange"
/>
</div>
</template>
@@ -0,0 +1,75 @@
<script setup lang="ts">
// Components
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
// Types
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
// Configs
import { config } from './list-cfg.protocol'
const searchQuery = ref('')
function handleSearch(event: Event) {
const target = event.target as HTMLInputElement
searchQuery.value = target.value
// TODO: Implement search logic here
// You can emit an event to parent or filter data directly
}
interface Props {
data: any[]
paginationMeta: PaginationMeta
}
defineProps<Props>()
const emit = defineEmits<{
pageChange: [page: number]
}>()
function handlePageChange(page: number) {
emit('pageChange', page)
}
</script>
<template>
<div class="space-y-4">
<!-- Title and Search Section -->
<div class="flex flex-col items-start">
<div class="flex items-center justify-between w-full">
<div>
<h2 class="mb-1 text-xl font-semibold">Protokol Kemoterapi</h2>
<p class="mb-4 text-sm text-gray-500">Rangkaian prosedur kemoterapi yang terintegrasi dan konsisten.</p>
</div>
<button class="rounded bg-orange-500 px-3 py-2 text-white hover:bg-orange-600">
<i class="ri-add-line"></i>
Tambah Protokol Kemoterapi
</button>
</div>
<div class="relative mt-10 w-72">
<input
v-model="searchQuery"
type="text"
placeholder="Cari protokol..."
class="w-full rounded-md border px-4 py-2 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
@input="handleSearch"
/>
<span class="absolute right-3 top-2.5 text-gray-400">
<i class="ri-search-line"></i>
</span>
</div>
</div>
<PubMyUiDataTable
v-bind="config"
:rows="data"
:skeleton-size="paginationMeta?.pageSize"
/>
<PaginationView
:pagination-meta="paginationMeta"
@page-change="handlePageChange"
/>
</div>
</template>
@@ -0,0 +1,71 @@
<script setup lang="ts">
// Components
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
// Types
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
// Configs
import { config } from './list-cfg.protocol'
const searchQuery = ref('')
function handleSearch(event: Event) {
const target = event.target as HTMLInputElement
searchQuery.value = target.value
// TODO: Implement search logic here
// You can emit an event to parent or filter data directly
}
interface Props {
data: any[]
paginationMeta: PaginationMeta
}
defineProps<Props>()
const emit = defineEmits<{
pageChange: [page: number]
}>()
function handlePageChange(page: number) {
emit('pageChange', page)
}
</script>
<template>
<div class="space-y-4">
<!-- Title and Search Section -->
<div class="flex flex-col items-start">
<div class="flex items-center justify-between w-full">
<div>
<h2 class="mb-1 text-xl font-semibold">Daftar Kunjungan Rawat Jalan Kemoterapi</h2>
<p class="mb-4 text-sm text-gray-500">Manajemen pendaftaran serta monitoring terapi pasien tindakan rawat jalan.</p>
</div>
</div>
<div class="relative mt-10 w-72">
<input
v-model="searchQuery"
type="text"
placeholder="Cari jadwal pemeriksaan..."
class="w-full rounded-md border px-4 py-2 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
@input="handleSearch"
/>
<span class="absolute right-3 top-2.5 text-gray-400">
<i class="ri-search-line"></i>
</span>
</div>
</div>
<PubMyUiDataTable
v-bind="config"
:rows="data"
:skeleton-size="paginationMeta?.pageSize"
/>
<PaginationView
:pagination-meta="paginationMeta"
@page-change="handlePageChange"
/>
</div>
</template>
@@ -0,0 +1,45 @@
<script setup lang="ts">
// Components
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
// Types
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
// Configs
import { config } from './list-cfg.visit'
interface Props {
data: any[]
paginationMeta: PaginationMeta
}
defineProps<Props>()
const emit = defineEmits<{
pageChange: [page: number]
verify: [rec: any]
}>()
function handlePageChange(page: number) {
emit('pageChange', page)
}
// Provide verify handler to child components via provide/inject
function handleVerify(rec: any) {
emit('verify', rec)
}
provide('verify-handler', handleVerify)
</script>
<template>
<div class="space-y-4">
<PubMyUiDataTable
v-bind="config"
:rows="data"
:skeleton-size="paginationMeta?.pageSize"
/>
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
</div>
</template>
+36
View File
@@ -0,0 +1,36 @@
<script setup lang="ts">
// Components
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
// Types
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
// Configs
import { config } from './list-cfg'
interface Props {
data: any[]
paginationMeta: PaginationMeta
}
defineProps<Props>()
const emit = defineEmits<{
pageChange: [page: number]
}>()
function handlePageChange(page: number) {
emit('pageChange', page)
}
</script>
<template>
<div class="space-y-4">
<PubMyUiDataTable
v-bind="config"
:rows="data"
:skeleton-size="paginationMeta?.pageSize"
/>
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
</div>
</template>
+49
View File
@@ -0,0 +1,49 @@
export type ChemotherapyData = {
id: number
tanggal: string
noRm: string
noBill: string
nama: string
jk: string
alamat: string
klinik: string
dokter: string
caraBayar: string
rujukan: string
ketRujukan: string
asal: string
}
export const sampleRows: ChemotherapyData[] = [
{
id: 1,
tanggal: '12 Agustus 2025',
noRm: 'RM23311224',
noBill: '-',
nama: 'Ahmad Baidowi',
jk: 'L',
alamat: 'Jl Jaksa Agung S. No. 9',
klinik: 'Penyakit dalam',
dokter: 'Dr. Andreas Sutaji',
caraBayar: 'JKN',
rujukan: 'Faskes BPJS',
ketRujukan: 'RUMAH SAKIT - RS Lawang Medika - Malang',
asal: 'Rawat Jalan Reguler',
},
{
id: 2,
tanggal: '11 Agustus 2025',
noRm: 'RM23455667',
noBill: '-',
nama: 'Abraham Sulaiman',
jk: 'L',
alamat: 'Purwantoro, Blimbing',
klinik: 'Penyakit dalam',
dokter: 'Dr. Andreas Sutaji',
caraBayar: 'JKN',
rujukan: 'Faskes BPJS',
ketRujukan: 'RUMAH SAKIT - RS Lawang Medika - Malang',
asal: 'Rawat Jalan Reguler',
},
// tambahkan lebih banyak baris contoh jika perlu
]
@@ -0,0 +1,28 @@
<script setup lang="ts">
import { Badge } from '~/components/pub/ui/badge'
const props = defineProps<{
rec: any
idx?: number
}>()
const statusMap: Record<string, { text: string; variant: 'default' | 'secondary' | 'fresh' | 'positive' | 'negative' | 'warning' | 'destructive' | 'outline' }> = {
'belum_terverifikasi': { text: 'Belum Terverifikasi', variant: 'secondary' },
'terverifikasi': { text: 'Terverifikasi', variant: 'positive' },
'ditolak': { text: 'Ditolak', variant: 'destructive' },
}
const statusInfo = computed(() => {
const status = props.rec.status?.toLowerCase() || props.rec.status_code?.toLowerCase() || 'belum_terverifikasi'
return statusMap[status] || statusMap['belum_terverifikasi']
})
</script>
<template>
<div class="flex justify-center">
<Badge :variant="statusInfo.variant">
{{ statusInfo.text }}
</Badge>
</div>
</template>
@@ -0,0 +1,31 @@
<script setup lang="ts">
import Button from '~/components/pub/ui/button/Button.vue'
const props = defineProps<{
rec: any
idx?: number
}>()
// Try to get verify handler from parent via inject
const verifyHandler = inject<(rec: any) => void>('verify-handler', null)
function handleVerify() {
if (verifyHandler) {
verifyHandler(props.rec)
}
}
</script>
<template>
<div class="flex justify-center">
<Button
type="button"
variant="outline"
class="border-orange-500 bg-orange-50 text-orange-600 hover:bg-orange-100 hover:text-orange-700"
@click="handleVerify"
>
Verifikasi
</Button>
</div>
</template>
@@ -0,0 +1,116 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import { differenceInDays, differenceInMonths, differenceInYears, parseISO } from 'date-fns'
import { Input } from '~/components/pub/ui/input'
import { cn } from '~/lib/utils'
import * as DE from '~/components/pub/my-ui/doc-entry'
const props = defineProps<{
fieldName?: string
label?: string
placeholder?: string
errors?: FormErrors
class?: string
selectClass?: string
fieldGroupClass?: string
labelClass?: string
isRequired?: boolean
}>()
const {
fieldName = 'birthDate',
label = 'Tanggal Lahir',
placeholder = 'Pilih tanggal lahir',
errors,
class: containerClass,
fieldGroupClass,
labelClass,
} = props
// Reactive variables for age calculation
const patientAge = ref<string>('Masukkan tanggal lahir')
// Function to calculate age with years, months, and days
function calculateAge(birthDate: string | Date | undefined): string {
if (!birthDate) {
return 'Masukkan tanggal lahir'
}
try {
let dateObj: Date
if (typeof birthDate === 'string') {
dateObj = parseISO(birthDate)
} else {
dateObj = birthDate
}
const today = new Date()
// Calculate years, months, and days
const totalYears = differenceInYears(today, dateObj)
// Calculate remaining months after years
const yearsPassed = new Date(dateObj)
yearsPassed.setFullYear(yearsPassed.getFullYear() + totalYears)
const remainingMonths = differenceInMonths(today, yearsPassed)
// Calculate remaining days after years and months
const monthsPassed = new Date(yearsPassed)
monthsPassed.setMonth(monthsPassed.getMonth() + remainingMonths)
const remainingDays = differenceInDays(today, monthsPassed)
// Format the result
const parts = []
if (totalYears > 0) parts.push(`${totalYears} Tahun`)
if (remainingMonths > 0) parts.push(`${remainingMonths} Bulan`)
if (remainingDays > 0) parts.push(`${remainingDays} Hari`)
return parts.length > 0 ? parts.join(' ') : '0 Hari'
} catch {
return 'Masukkan tanggal lahir'
}
}
</script>
<template>
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
<DE.Label
:label-for="fieldName"
:class="cn('select-field-label', labelClass)"
:is-required="isRequired"
>
{{ label }}
</DE.Label>
<DE.Field
:id="fieldName"
:errors="errors"
:class="cn('select-field-wrapper')"
>
<FormField
v-slot="{ componentField }"
:name="fieldName"
>
<FormItem>
<FormControl>
<Input
id="birthDate"
type="date"
min="1900-01-01"
v-bind="componentField"
:placeholder="placeholder"
@update:model-value="
(value: string | number) => {
const dateStr = typeof value === 'number' ? String(value) : value
patientAge = calculateAge(dateStr)
}
"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</DE.Field>
</DE.Cell>
</template>
@@ -0,0 +1,98 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
import { cn, mapToComboboxOptList } from '~/lib/utils'
import { occupationCodes } from '~/lib/constants'
import { getValueLabelList as getDoctorLabelList } from '~/services/doctor.service'
import * as DE from '~/components/pub/my-ui/doc-entry'
import type { Item } from '~/components/pub/my-ui/combobox'
const props = defineProps<{
fieldName?: string
label?: string
placeholder?: string
errors?: FormErrors
class?: string
selectClass?: string
fieldGroupClass?: string
labelClass?: string
isRequired?: boolean
}>()
const {
fieldName = 'job',
label = 'Pekerjaan',
placeholder = 'Pilih pekerjaan',
errors,
class: containerClass,
fieldGroupClass,
labelClass,
} = props
const doctors = ref<Array<Item>>([])
async function fetchDpjp(specialistId: string, subspecialistId: string) {
doctors.value = await getDoctorLabelList({
serviceType: 1,
serviceDate: new Date().toISOString().substring(0, 10),
includes: 'employee-person',
// "unit-id": parseInt(unitId),
"specialist-code": String(specialistId),
"subspecialist-code": String(subspecialistId),
}, true)
}
// const selectedUnitId = inject<Ref<string | null>>("selectedUnitId")!
const selectedSpecialistId = inject<Ref<string | null>>("selectedSpecialistId")!
const selectedSubSpecialistId = inject<Ref<string | null>>("selectedSubSpecialistId")!
// function handleDpjpChange(selected: string) {
// selectedDpjpId.value = selected ?? null
// }
watch([ selectedSpecialistId, selectedSubSpecialistId], () => {
if (selectedSpecialistId.value && selectedSubSpecialistId.value) {
console.log(`Select Doctor`)
fetchDpjp( selectedSpecialistId.value, selectedSubSpecialistId.value)
}
})
</script>
<template>
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
<DE.Label
:label-for="fieldName"
:class="cn('select-field-label', labelClass)"
:is-required="isRequired"
>
{{ label }}
</DE.Label>
<DE.Field
:id="fieldName"
:errors="errors"
:class="cn('select-field-wrapper')"
>
<FormField
v-slot="{ componentField }"
:name="fieldName"
>
<FormItem>
<FormControl>
<Combobox
class="focus:ring-0 focus:ring-offset-0"
:id="fieldName"
v-bind="componentField"
:items="doctors"
:placeholder="placeholder"
search-placeholder="Cari..."
empty-message="Data tidak ditemukan"
:is-disabled="selectedSubSpecialistId === null"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</DE.Field>
</DE.Cell>
</template>
@@ -0,0 +1,98 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
import { cn, mapToComboboxOptList } from '~/lib/utils'
import { occupationCodes } from '~/lib/constants'
import type { Item } from '~/components/pub/my-ui/combobox'
import { getValueLabelList as getSpecialistLabelList } from '~/services/specialist.service'
import { getValueLabelList as getSubspecialistLabelList } from '~/services/subspecialist.service'
import * as DE from '~/components/pub/my-ui/doc-entry'
const props = defineProps<{
fieldName?: string
label?: string
placeholder?: string
errors?: FormErrors
class?: string
selectClass?: string
fieldGroupClass?: string
labelClass?: string
isRequired?: boolean
}>()
const {
fieldName = 'job',
label = 'Pekerjaan',
placeholder = 'Pilih pekerjaan',
errors,
class: containerClass,
fieldGroupClass,
labelClass,
} = props
const specialists = ref<Array<Item>>([])
async function fetchSpecialists(unitId: string) {
specialists.value = await getSpecialistLabelList({
serviceType: 1,
serviceDate: new Date().toISOString().substring(0, 10),
specialistCode: 0,
"unit-id": String(unitId),
}, true)
}
const selectedUnitId = inject<Ref<string | null>>("selectedUnitId")!
const selectedSpecialistId = inject<Ref<string | null>>("selectedSpecialistId")!
function handleSpecialistChange(selected: string) {
selectedSpecialistId.value = selected ?? null
}
watch([selectedUnitId], () => {
if (selectedUnitId.value) {
fetchSpecialists(selectedUnitId.value)
}
})
</script>
<template>
<DE.Block :class="cn('select-field-group', fieldGroupClass, containerClass)">
<div>
<DE.Label
:label-for="fieldName"
:class="cn('select-field-label', labelClass)"
:is-required="isRequired"
>
Spesialis
</DE.Label>
<DE.Field
:id="fieldName"
:errors="errors"
:class="cn('select-field-wrapper')"
>
<FormField
v-slot="{ componentField }"
:name="fieldName"
>
<FormItem>
<FormControl>
<Combobox
class="focus:ring-0 focus:ring-offset-0"
:id="fieldName"
v-bind="componentField"
:items="specialists"
:placeholder="placeholder"
search-placeholder="Cari..."
empty-message="Data tidak ditemukan"
@update:model-value="handleSpecialistChange"
:is-disabled="selectedUnitId === null"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</DE.Field>
</div>
</DE.Block>
</template>
@@ -0,0 +1,97 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
import { cn, mapToComboboxOptList } from '~/lib/utils'
import { occupationCodes } from '~/lib/constants'
import type { Item } from '~/components/pub/my-ui/combobox'
import { getValueLabelList as getSubspecialistLabelList } from '~/services/subspecialist.service'
import * as DE from '~/components/pub/my-ui/doc-entry'
const props = defineProps<{
fieldName?: string
label?: string
placeholder?: string
errors?: FormErrors
class?: string
selectClass?: string
fieldGroupClass?: string
labelClass?: string
isRequired?: boolean
}>()
const {
fieldName = 'job',
label = 'Pekerjaan',
placeholder = 'Pilih pekerjaan',
errors,
class: containerClass,
fieldGroupClass,
labelClass,
} = props
const subspecialists = ref<Array<Item>>([])
async function fetchSubSpecialists(specialistId: string) {
subspecialists.value = await getSubspecialistLabelList({
serviceType: 1,
serviceDate: new Date().toISOString().substring(0, 10),
specialistCode: 0,
"specialist-code": String(specialistId),
}, true)
}
const selectedSpecialistId = inject<Ref<string | null>>("selectedSpecialistId")!
const selectedSubSpecialistId = inject<Ref<string | null>>("selectedSubSpecialistId")!
function handleSubSpecialistChange(selected: string) {
selectedSubSpecialistId.value = selected ?? null
}
watch([selectedSpecialistId], () => {
if (selectedSpecialistId.value) {
fetchSubSpecialists(selectedSpecialistId.value)
}
})
</script>
<template>
<DE.Block :class="cn('select-field-group', fieldGroupClass, containerClass)">
<div>
<DE.Label
:label-for="fieldName"
:class="cn('select-field-label', labelClass)"
:is-required="isRequired"
>
Sub Spesialis
</DE.Label>
<DE.Field
:id="fieldName"
:errors="errors"
:class="cn('select-field-wrapper')"
>
<FormField
v-slot="{ componentField }"
:name="fieldName"
>
<FormItem>
<FormControl>
<Combobox
class="focus:ring-0 focus:ring-offset-0"
:id="fieldName"
v-bind="componentField"
:items="subspecialists"
:placeholder="placeholder"
search-placeholder="Cari..."
empty-message="Data tidak ditemukan"
@update:model-value="handleSubSpecialistChange"
:is-disabled="selectedSpecialistId === null"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</DE.Field>
</div>
</DE.Block>
</template>
@@ -0,0 +1,85 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
import { cn, mapToComboboxOptList } from '~/lib/utils'
import { occupationCodes } from '~/lib/constants'
import { getValueLabelList as getUnitLabelList } from '~/services/unit.service'
import * as DE from '~/components/pub/my-ui/doc-entry'
import type { Item } from '~/components/pub/my-ui/combobox'
const props = defineProps<{
fieldName?: string
label?: string
placeholder?: string
errors?: FormErrors
class?: string
selectClass?: string
fieldGroupClass?: string
labelClass?: string
isRequired?: boolean
}>()
const {
fieldName = 'job',
label = 'Pekerjaan',
placeholder = 'Pilih pekerjaan',
errors,
class: containerClass,
fieldGroupClass,
labelClass,
} = props
const units = ref<Array<Item>>([])
async function fetchData() {
units.value = await getUnitLabelList({}, true)
}
const selectedUnitId = inject<Ref<string | null>>("selectedUnitId")!
function handleDataChange(selected: string) {
selectedUnitId.value = selected ?? null
}
onMounted(() => {
fetchData()
})
</script>
<template>
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
<DE.Label
:label-for="fieldName"
:class="cn('select-field-label', labelClass)"
:is-required="isRequired"
>
{{ label }}
</DE.Label>
<DE.Field
:id="fieldName"
:errors="errors"
:class="cn('select-field-wrapper')"
>
<FormField
v-slot="{ componentField }"
:name="fieldName"
>
<FormItem>
<FormControl>
<Combobox
class="focus:ring-0 focus:ring-offset-0"
:id="fieldName"
v-bind="componentField"
:items="units"
:placeholder="placeholder"
search-placeholder="Cari..."
empty-message="Data tidak ditemukan"
@update:model-value="handleDataChange"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</DE.Field>
</DE.Cell>
</template>
@@ -0,0 +1,94 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import { toTypedSchema } from '@vee-validate/zod'
import { Form } from '~/components/pub/ui/form'
import SelectDate from './_common/select-date.vue'
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
import SelectSpeciality from './_common/select-specialist.vue'
import SelectDpjp from './_common/select-dpjp.vue'
import * as DE from '~/components/pub/my-ui/doc-entry'
import SelectUnit from './_common/select-unit.vue'
import SelectSubspecialist from './_common/select-subspecialist.vue'
import SelectSpecialist from './_common/select-specialist.vue'
const props = defineProps<{
schema: any
initialValues?: any
errors?: FormErrors
selectedUnitId?: number | null
selectedSpecialistId?: number | null
selectedSubSpecialistId?: number | null
}>()
const formSchema = toTypedSchema(props.schema)
const formRef = ref()
defineExpose({
validate: () => formRef.value?.validate(),
resetForm: () => formRef.value?.resetForm(),
setValues: (values: any, shouldValidate = true) => formRef.value?.setValues(values, shouldValidate),
values: computed(() => formRef.value?.values),
})
</script>
<template>
<Form
ref="formRef"
v-slot="{ values }"
as=""
keep-values
:validation-schema="formSchema"
:validate-on-mount="false"
validation-mode="onSubmit"
:initial-values="initialValues ? initialValues : {}"
>
<DE.Block :col-count="2" :cell-flex="false">
<InputBase
field-name="sepStatus"
label="Status Sep"
placeholder="Status Sep"
:is-disabled="true"
/>
<SelectDate
field-name="date"
label="Tanggal Rencana Kontrol"
:errors="errors"
is-required
/>
<DE.Cell :col-span="2">
<DE.Block :col-count="4" :cell-flex="false">
<SelectUnit
field-name="unit_code"
label="Unit"
placeholder="Pilih Unit"
:errors="errors"
is-required
/>
<SelectSpecialist
field-name="specialist_code"
label="Spesialis/Sub Spesialis"
placeholder="Pilih Spesialis/Sub Spesialis"
:errors="errors"
is-required
/>
<SelectSubspecialist
field-name="subspecialist_code"
label="Spesialis/Sub Spesialis"
placeholder="Pilih Spesialis/Sub Spesialis"
:errors="errors"
is-required
/>
<SelectDpjp
field-name="doctor_code"
label="DPJP"
placeholder="Pilih DPJP"
:errors="errors"
is-required
/>
</DE.Block>
</DE.Cell>
</DE.Block>
</Form>
</template>
@@ -0,0 +1,64 @@
import type { Config } from '~/components/pub/my-ui/data-table'
import type { Patient } from '~/models/patient'
import { defineAsyncComponent } from 'vue'
import { educationCodes, genderCodes } from '~/lib/constants'
import { calculateAge } from '~/lib/utils'
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
export const config: Config = {
cols: [{width: 180}, {}, {}, {}, {}, {width: 30},],
headers: [
[
{ label: 'Tgl Rencana Kontrol' },
{ label: 'Spesialis' },
{ label: 'Sub Spesialis' },
{ label: 'DPJP' },
{ label: 'Status SEP' },
{ label: 'Action' },
],
],
keys: ['date', 'specialist.name', 'subspecialist.name', 'doctor.employee.person.name', 'sep_status', 'action'],
delKeyNames: [
{ key: 'code', label: 'Kode' },
{ key: 'name', label: 'Nama' },
],
parses: {
date: (rec: unknown): unknown => {
const date = (rec as any).date
if (typeof date == 'object' && date) {
return (date as Date).toLocaleDateString('id-ID')
} else if (typeof date == 'string') {
return (date as string).substring(0, 10)
}
return date
},
specialist_subspecialist: (rec: unknown): unknown => {
return '-'
},
dpjp: (rec: unknown): unknown => {
// const { person } = rec as Patient
return '-'
},
},
components: {
action(rec, idx) {
return {
idx,
rec: rec as object,
component: action,
}
},
},
htmls: {
sep_status(_rec) {
return 'SEP Internal'
},
},
}
@@ -0,0 +1,31 @@
<script setup lang="ts">
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
import { config } from './list.cfg'
interface Props {
data: any[]
paginationMeta: PaginationMeta
}
defineProps<Props>()
const emit = defineEmits<{
pageChange: [page: number]
}>()
function handlePageChange(page: number) {
emit('pageChange', page)
}
</script>
<template>
<div class="space-y-4">
<PubMyUiDataTable
v-bind="config"
:rows="data"
:skeleton-size="paginationMeta?.pageSize"
/>
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
</div>
</template>
@@ -0,0 +1,54 @@
<script setup lang="ts">
import DetailRow from '~/components/pub/my-ui/form/view/detail-row.vue'
import { cn, } from '~/lib/utils'
import type { ControlLetter } from '~/models/control-letter'
// #region Props & Emits
const props = defineProps<{
instance: ControlLetter | null
}>()
const emit = defineEmits<{
(e: 'click', type: string): void
}>()
// #endregion
// #region State & Computed
// #endregion
// Computed addresses from nested data
// #endregion
// #region Lifecycle Hooks
// #endregion
// #region Functions
// #endregion region
// #region Utilities & event handlers
function onClick(type: string) {
emit('click', type)
}
// #endregion
// #region Watchers
// #endregion
</script>
<template>
<div :class="cn('min-h-[50vh] space-y-2',)">
<DetailRow label="Tgl Rencana Kontrol">{{ props.instance?.date ? new Date(props.instance?.date).toLocaleDateString('id-ID') : '-' }}</DetailRow>
<DetailRow label="Unit">{{ props.instance?.unit.name || '-' }}</DetailRow>
<DetailRow label="Spesialis">{{ props.instance?.specialist.name || '-' }}</DetailRow>
<DetailRow label="Sub Spesialis">{{ props.instance?.subspecialist.name || '-' }}</DetailRow>
<DetailRow label="DPJP">{{ props.instance?.doctor.employee.person.name || '-' }}</DetailRow>
<DetailRow label="Status SEP">{{ 'SEP INTERNAL' }}</DetailRow>
</div>
<div class="border-t-1 my-2 flex justify-end border-t-slate-300 py-2">
<PubMyUiNavFooterBaEd @click="onClick" />
</div>
</template>
<style scoped></style>
@@ -0,0 +1,43 @@
import type { Config } from '~/components/pub/my-ui/data-table'
import { defineAsyncComponent } from 'vue'
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
const input = defineAsyncComponent(() => import('~/components/pub/ui/input/Input.vue'))
export const config: Config = {
cols: [{}, {}, { classVal: '!p-0.5' }, { width: 50 }],
headers: [
[
{ label: 'Nama' },
{ label: 'Jenis' },
{ label: 'Catatan' },
{ label: '' },
],
],
keys: ['mcuSrc.name', 'mcuSrc.mcuSrcCategory.name', 'note'],
delKeyNames: [
{ key: 'mcuSrc.name', label: 'Nama' },
],
components: {
note(rec, idx) {
return {
idx,
rec: rec as object,
component: input,
}
},
action(rec, idx) {
return {
idx,
rec: rec as object,
component: action,
}
},
},
htmls: {},
}
@@ -0,0 +1,26 @@
<script setup lang="ts">
import { config } from './list-entry.cfg'
import type { McuOrderItem } from '~/models/mcu-order-item';
defineProps<{
data: McuOrderItem[]
}>()
const emit = defineEmits<{
requestItem: []
}>()
</script>
<template>
<PubMyUiDataTable class="border mb-3 2xl:mb-4"
v-bind="config"
:rows="data"
/>
<div class="-mx-1 [&_button]:mx-1">
<Button @click="emit('requestItem')">
<Icon name="i-lucide-plus" />
Pilih Item
</Button>
</div>
</template>
@@ -0,0 +1,18 @@
import type { Config } from '~/components/pub/my-ui/data-table'
export const config: Config = {
cols: [{}, {}],
headers: [
[
{ label: 'Nama' },
{ label: 'Jenis' },
],
],
keys: ['mcuSrc.name', 'mcuSrcCategory.name'],
delKeyNames: [
{ key: 'mcuSrc.name', label: 'Nama' },
],
}
@@ -0,0 +1,20 @@
<script setup lang="ts">
import type { McuOrderItem } from '~/models/mcu-order-item';
import { config } from './list.cfg'
defineProps<{
data: McuOrderItem[]
}>()
const emit = defineEmits<{
tambah: [mode: string]
}>()
</script>
<template>
<PubMyUiDataTable class="border"
v-bind="config"
:rows="data"
/>
</template>
+32
View File
@@ -0,0 +1,32 @@
<script setup lang="ts">
import * as DE from '~/components/pub/my-ui/doc-entry'
import type { McuOrder } from '~/models/mcu-order';
const props = defineProps<{
data: McuOrder
}>()
</script>
<template>
<div class="text-sm 2xl:text-base font-semibold mb-3">
Order {{ data?.createdAt?.substring(0, 10) }} - {{ data.status_code }}
</div>
<div class="max-w-[1000px]">
<DE.Block mode="preview" :col-count=5 class="!mb-3">
<DE.Cell :col-span="2">
<DE.Label class="font-semibold">DPJP</DE.Label>
<DE.Field>
{{ data?.doctor?.employee?.person?.name || '.........' }}
</DE.Field>
</DE.Cell>
<DE.Cell></DE.Cell>
<DE.Cell :col-span="2">
<DE.Label class="font-semibold">PPDS</DE.Label>
<DE.Field>
...........
</DE.Field>
</DE.Cell>
</DE.Block>
</div>
</template>
+64
View File
@@ -0,0 +1,64 @@
<script setup lang="ts">
import * as DE from '~/components/pub/my-ui/doc-entry';
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
import Nav from '~/components/pub/my-ui/nav-footer/ca-ed-su.vue'
import type { McuOrder } from '~/models/mcu-order';
import McuOrderItems from '~/components/app/mcu-order-item/list.vue';
interface Props {
data: McuOrder[]
paginationMeta: PaginationMeta
}
const props = defineProps<Props>()
const emit = defineEmits<{
cancel: [data: any]
edit: [data: any],
submit: [data: any]
}>()
function navClick(type: 'cancel' | 'edit' | 'submit', data: McuOrder): void {
if (type === 'cancel') {
emit('cancel', data)
} else if (type === 'edit') {
emit('edit', data)
} else if (type === 'submit') {
emit('submit', data)
}
}
</script>
<template>
<div v-if="data.length == 0" class="p-10 text-center">
<div class="mb-4 xl:mb-5">Belum Ada Data</div>
</div>
<template v-for="item, idx in data">
<div :class="'text-sm 2xl:text-base font-semibold ' + (item.status_code == 'new' ? 'mb-2' : 'mb-2')">
Order #{{ data.length - idx }} - {{ item.createdAt?.substring(0, 10) }} - {{ item.status_code }}
</div>
<DE.Block mode="preview" :col-count=7 class="!mb-3">
<DE.Cell :col-span="3">
<DE.Label :class="'font-semibold ' + (item.status_code == 'new' ? 'pt-2' : '')">DPJP</DE.Label>
<DE.Field :class="item.status_code == 'new' ? 'pt-2' : ''">
{{ item.doctor?.employee?.person?.name || '........' }}
</DE.Field>
</DE.Cell>
<DE.Cell :col-span="3">
<DE.Label :class="'font-semibold ' + (item.status_code == 'new' ? 'pt-2' : '')">PPDS</DE.Label>
<DE.Field :class="item.status_code == 'new' ? 'pt-2' : ''">
...........
</DE.Field>
</DE.Cell>
<div class="flex justify-end" >
<Nav
v-if="item.status_code == 'new'"
:small-mode="true"
:default-class="'flex gap-1'"
@click="(type) => { navClick(type, item) }"
/>
</div>
</DE.Block>
<McuOrderItems :data="item.items || []" @click="console.log('click')" class="!mb-5" />
</template>
</template>
+64
View File
@@ -0,0 +1,64 @@
<script setup lang="ts">
import * as DE from '~/components/pub/my-ui/doc-entry';
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
import Nav from '~/components/pub/my-ui/nav-footer/ca-ed-su.vue'
import type { McuOrder } from '~/models/mcu-order';
import McuOrderItems from '~/components/app/mcu-order-item/list.vue';
interface Props {
data: McuOrder[]
paginationMeta: PaginationMeta
}
const props = defineProps<Props>()
const emit = defineEmits<{
cancel: [data: any]
edit: [data: any],
submit: [data: any]
}>()
function navClick(type: 'cancel' | 'edit' | 'submit', data: McuOrder): void {
if (type === 'cancel') {
emit('cancel', data)
} else if (type === 'edit') {
emit('edit', data)
} else if (type === 'submit') {
emit('submit', data)
}
}
</script>
<template>
<div v-if="data.length == 0" class="p-10 text-center">
<div class="mb-4 xl:mb-5">Belum Ada Data</div>
</div>
<template v-for="item, idx in data">
<div :class="'text-sm 2xl:text-base font-semibold ' + (item.status_code == 'new' ? 'mb-2' : 'mb-2')">
Order #{{ data.length - idx }} - {{ item.createdAt?.substring(0, 10) }} - {{ item.status_code }}
</div>
<DE.Block mode="preview" :col-count=7 class="!mb-3">
<DE.Cell :col-span="3">
<DE.Label :class="'font-semibold ' + (item.status_code == 'new' ? 'pt-2' : '')">DPJP</DE.Label>
<DE.Field :class="item.status_code == 'new' ? 'pt-2' : ''">
{{ item.doctor?.employee?.person?.name || '........' }}
</DE.Field>
</DE.Cell>
<DE.Cell :col-span="3">
<DE.Label :class="'font-semibold ' + (item.status_code == 'new' ? 'pt-2' : '')">PPDS</DE.Label>
<DE.Field :class="item.status_code == 'new' ? 'pt-2' : ''">
...........
</DE.Field>
</DE.Cell>
<div class="flex justify-end" >
<Nav
v-if="item.status_code == 'new'"
:small-mode="true"
:default-class="'flex gap-1'"
@click="(type) => { navClick(type, item) }"
/>
</div>
</DE.Block>
<McuOrderItems :data="item.items || []" @click="console.log('click')" class="!mb-5" />
</template>
</template>
@@ -0,0 +1,35 @@
<script setup lang="ts">
import * as DE from '~/components/pub/my-ui/doc-entry'
import type { McuSrcCategory } from '~/models/mcu-src-category';
const model = defineModel()
const props = defineProps<{
data: McuSrcCategory[]
}>()
const emit = defineEmits<{
pick: [category: McuSrcCategory]
}>()
if (!model.value && props.data.length > 0) {
model.value = props.data[0]?.code
}
function pick(category: McuSrcCategory) {
model.value = category.code
emit('pick', category)
}
</script>
<template>
<div class="mb-5">
<div class="font-semibold mb-1.5">
Kategori
</div>
<div class="-mx-1 [&_button]:mx-1 ">
<Button v-for="item, idx in data" :variant="model === item.code ? 'default' : 'outline'" @click="pick(item)">
{{ item.name }}
</Button>
</div>
</div>
</template>
@@ -0,0 +1,49 @@
<script setup lang="ts">
import type { McuSrc } from '~/models/mcu-src';
import type { McuOrderItem } from '~/models/mcu-order-item';
const data = defineModel({ type: Array as PropType<McuOrderItem[]>, required: true })
defineProps<{
dataSource: McuSrc[]
// data: number[]
}>()
const emit = defineEmits<{
pick: [item: McuSrc]
}>()
function pick(item: McuSrc) {
emit('pick', item)
// if (data.value.some(e => e.mcuSrc_id === item.id)) {
// const pos = data.value.map(e => e.mcuSrc_id).indexOf(item.id)
// data.value.splice(pos, 1)
// } else {
// data.value.push({
// id: 0,
// mcuOrder_id: 0,
// mcuSrc_id: item.id,
// createdAt: "",
// updatedAt: "",
// })
// }
}
</script>
<template>
<div class="mb-5">
<div class="font-semibold mb-1.5">
Daftar Item
</div>
<div class="grid lg:grid-cols-4 2xl:grid-cols-5 gap-2 [&_button]:w-full">
<div v-for="item, idx in dataSource" :key="idx" class="flex gap-2">
<Button
:variant="data.some(e => e.mcuSrc_id === item.id) ? 'default' : 'outline'"
type="button"
@click="pick(item)"
>
{{ item.name }}
</Button>
</div>
</div>
</div>
</template>
@@ -0,0 +1,50 @@
import type {
Col,
KeyLabel,
RecComponent,
RecStrFuncComponent,
RecStrFuncUnknown,
Th,
} from '~/components/pub/my-ui/data/types'
import { defineAsyncComponent } from 'vue'
type SmallDetailDto = any
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
export const cols: Col[] = [{}, {}, {}, {}, {}, {}, { width: 50 }]
export const header: Th[][] = [
[
{ label: 'Nama' },
{ label: "Dosis" },
{ label: 'Satuan' },
{ label: '' },
],
]
export const keys = ['name', 'dose', 'uom.name', 'action']
export const delKeyNames: KeyLabel[] = [
{ key: 'code', label: 'Kode' },
{ key: 'name', label: 'Nama' },
]
export const funcParsed: RecStrFuncUnknown = {
group: (rec: unknown): unknown => {
return (rec as SmallDetailDto).medicineGroup_code || '-'
},
}
export const funcComponent: RecStrFuncComponent = {
action: (rec: unknown, idx: number): RecComponent => {
const res: RecComponent = {
idx,
rec: rec as object,
component: action,
}
return res
},
}
export const funcHtml: RecStrFuncUnknown = {}
@@ -0,0 +1,35 @@
<script setup lang="ts">
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-entry'
interface Props {
data: any[]
paginationMeta: PaginationMeta
}
defineProps<Props>()
const emit = defineEmits<{
pageChange: [page: number]
}>()
function handlePageChange(page: number) {
emit('pageChange', page)
}
</script>
<template>
<div class="space-y-4">
<PubBaseDataTable
:rows="data"
:cols="cols"
:header="header"
:keys="keys"
:func-parsed="funcParsed"
:func-html="funcHtml"
:func-component="funcComponent"
/>
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
</div>
</template>
@@ -1,14 +1,30 @@
<script setup lang="ts">
import type { PrescriptionItem } from '~/models/prescription-item';
import { config } from './list-entry.cfg'
defineProps<{
data: any[]
data: PrescriptionItem[]
}>()
const emit = defineEmits<{
add: [mode: 'mix' | 'non-mix']
}>()
</script>
<template>
<PubMyUiDataTable
<PubMyUiDataTable class="border mb-3 2xl:mb-4"
v-bind="config"
:rows="data"
/>
<div class="-mx-1 [&_button]:mx-1">
<Button @click="emit('add', 'mix')">
<Icon name="i-lucide-plus" />
Tambah Racikan
</Button>
<Button @click="emit('add', 'non-mix')">
<Icon name="i-lucide-plus" />
Tambah Non Racikan
</Button>
</div>
</template>
@@ -0,0 +1,41 @@
import type { Config } from '~/components/pub/my-ui/data-table'
import { defineAsyncComponent } from 'vue'
type SmallDetailDto = any
export const config: Config = {
cols: [{}, {}, {}, {}, {}, {}],
headers: [
[
{ label: 'Nama' },
{ label: 'Bentuk' },
{ label: 'Freq' },
{ label: 'Dosis' },
{ label: 'Interval' },
{ label: 'Total' },
],
],
keys: ['name', 'uom_code', 'frequency', 'multiplier', 'interval', 'total'],
delKeyNames: [
{ key: 'code', label: 'Kode' },
{ key: 'name', label: 'Nama' },
],
parses: {
cateogry: (rec: unknown): unknown => {
return (rec as SmallDetailDto).medicineCategory?.name || '-'
},
group: (rec: unknown): unknown => {
return (rec as SmallDetailDto).medicineGroup?.name || '-'
},
method: (rec: unknown): unknown => {
return (rec as SmallDetailDto).medicineMethod?.name || '-'
},
unit: (rec: unknown): unknown => {
return (rec as SmallDetailDto).medicineUnit?.name || '-'
},
},
}
@@ -0,0 +1,20 @@
<script setup lang="ts">
import type { PrescriptionItem } from '~/models/prescription-item';
import { config } from './list.cfg'
defineProps<{
data: PrescriptionItem[]
}>()
const emit = defineEmits<{
tambah: [mode: string]
}>()
</script>
<template>
<PubMyUiDataTable class="border mb-2 2xl:mb-3"
v-bind="config"
:rows="data"
/>
</template>
@@ -0,0 +1,113 @@
<script setup lang="ts">
import { LucidePlus } from 'lucide-vue-next';
import * as DE from '~/components/pub/my-ui/doc-entry'
import Separator from '~/components/pub/ui/separator/Separator.vue';
import * as Table from '~/components/pub/ui/table'
import Nav from '~/components/pub/my-ui/nav-footer/cl-sa.vue'
import { genBase } from '~/models/_base';
import { genMedicine } from '~/models/medicine';
import type { MedicinemixItem } from '~/models/medicinemix-item';
import type { PrescriptionItem } from '~/models/prescription-item';
const props = defineProps<{
data: PrescriptionItem
items: MedicinemixItem[]
}>()
type ClickType = 'close' | 'save'
const emit = defineEmits<{
close: [],
save: [data: PrescriptionItem, items: MedicinemixItem[]],
}>()
function navClick(type: ClickType) {
if (type === 'close') {
emit('close')
} else if (type === 'save') {
emit('save', props.data, props.items)
}
}
function addItem() {
props.items.push({
...genBase(),
medicineMix_id: 0,
medicine_id: 0,
medicine: genMedicine(),
dose: 0,
uom_code: '',
})
}
</script>
<template>
<DE.Block :colCount="5" :cellFlex="false">
<DE.Cell :colSpan="5">
<DE.Label>Nama</DE.Label>
<DE.Field><Input :value="data.medicineMix?.name" /></DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label>Frequensi</DE.Label>
<DE.Field><Input v-model="data.frequency" /></DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label>Dosis</DE.Label>
<DE.Field><Input v-model="data.dose" /></DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label>Sediaan</DE.Label>
<DE.Field><Input :value="data.medicineMix?.uom_code" /></DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label>Total</DE.Label>
<DE.Field><Input /></DE.Field>
</DE.Cell>
<DE.Cell :colSpan="5">
<DE.Label>Cara Pakai</DE.Label>
<DE.Field><Input /></DE.Field>
</DE.Cell>
</DE.Block>
<div class="text-sm 2xl:text-base font-semibold !mb-3">Daftar Obat</div>
<Table.Table class="border mb-3 2xl:mb-4">
<Table.TableHeader class="[&_th]:h-8 [&_th]:2xl:h-9">
<Table.TableRow>
<Table.TableHead>Nama</Table.TableHead>
<Table.TableHead class="w-24">Dosis</Table.TableHead>
<Table.TableHead class="w-24">Satuan</Table.TableHead>
<Table.TableHead class="w-20">..</Table.TableHead>
</Table.TableRow>
</Table.TableHeader>
<Table.TableBody class="[&_td]:p-0.6">
<Table.TableRow v-if="items.length > 0" v-for="item in items">
<Table.TableCell>
<Input v-model="item.medicine.name" />
</Table.TableCell>
<Table.TableCell>
<Input v-model="item.dose" />
</Table.TableCell>
<Table.TableCell>
<Input />
</Table.TableCell>
</Table.TableRow>
<Table.TableRow v-else>
<Table.TableCell colspan="4" class="!p-5 text-center">
Belum ada data
</Table.TableCell>
</Table.TableRow>
</Table.TableBody>
</Table.Table>
<div>
<Button @click="addItem">
<LucidePlus />
Tambah
</Button>
</div>
<Separator class="my-5" />
<div class="flex justify-center">
<Nav @click="navClick" />
</div>
</template>
@@ -0,0 +1,90 @@
<script setup lang="ts">
import * as DE from '~/components/pub/my-ui/doc-entry'
import Separator from '~/components/pub/ui/separator/Separator.vue'
import Nav from '~/components/pub/my-ui/nav-footer/cl-sa.vue'
import { bigTimeUnitCodes } from '~/lib/constants'
import type { PrescriptionItem } from '~/models/prescription-item'
const props = defineProps<{
data: PrescriptionItem
}>()
type ClickType = 'close' | 'save'
type Item = {
value: string
label: string
}
const bigTimeUnitCodeItems: Item[] = []
if(!props.data.intervalUnit_code) {
props.data.intervalUnit_code = 'day'
}
Object.keys(bigTimeUnitCodes).forEach((key) => {
bigTimeUnitCodeItems.push({
value: key,
label: bigTimeUnitCodes[key] || '',
})
})
const emit = defineEmits<{
close: [],
save: [data: PrescriptionItem],
}>()
function navClick(type: ClickType) {
if (type === 'close') {
emit('close')
} else if (type === 'save') {
emit('save', props.data)
}
}
</script>
<template>
<DE.Block :colCount="5" :cellFlex="false">
<DE.Cell :colSpan="5">
<DE.Label>Nama</DE.Label>
<DE.Field><Input :value="data.medicineMix?.name" /></DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label>Frequensi</DE.Label>
<DE.Field><Input type="number" v-model.number="data.frequency" /></DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label>Dosis</DE.Label>
<DE.Field><Input type="number" v-model.number="data.dose" /></DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label>Sediaan</DE.Label>
<DE.Field><Input :value="data.medicineMix?.uom_code" readonly /></DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label>Interval</DE.Label>
<DE.Field>
<Select
v-model="data.intervalUnit_code"
:items="bigTimeUnitCodeItems"
/>
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label>Total</DE.Label>
<DE.Field>
<Input v-model="data.quantity" />
</DE.Field>
</DE.Cell>
<DE.Cell :colSpan="5">
<DE.Label>Cara Pakai</DE.Label>
<DE.Field><Input /></DE.Field>
</DE.Cell>
</DE.Block>
<Separator class="my-5" />
<div class="flex justify-center">
<Nav @click="navClick" />
</div>
</template>
@@ -0,0 +1,32 @@
<script setup lang="ts">
import * as DE from '~/components/pub/my-ui/doc-entry'
import type { Prescription } from '~/models/prescription'
const props = defineProps<{
data: Prescription
}>()
</script>
<template>
<div class="text-sm 2xl:text-base font-semibold mb-3">
Order {{ data.issuedAt?.substring(0, 10) || data.createdAt?.substring(0, 10) }} - {{ data.status_code }}
</div>
<div class="max-w-[1000px]">
<DE.Block mode="preview" :col-count=5 class="!mb-3">
<DE.Cell :col-span="2">
<DE.Label class="font-semibold">DPJP</DE.Label>
<DE.Field>
{{ data.doctor?.employee?.person?.name || '.........' }}
</DE.Field>
</DE.Cell>
<DE.Cell></DE.Cell>
<DE.Cell :col-span="2">
<DE.Label class="font-semibold">PPDS</DE.Label>
<DE.Field>
...........
</DE.Field>
</DE.Cell>
</DE.Block>
</div>
</template>
+33 -29
View File
@@ -1,32 +1,36 @@
<template>
<div>
<PubMyUiDocEntryBlock mode="preview" :colCount=3>
<PubMyUiDocEntryCell>
<PubMyUiDocEntryLabel>DPJP</PubMyUiDocEntryLabel>
<PubMyUiDocEntryField>
<Input />
</PubMyUiDocEntryField>
</PubMyUiDocEntryCell>
<PubMyUiDocEntryCell />
<PubMyUiDocEntryCell>
<PubMyUiDocEntryLabel>Tgl Order</PubMyUiDocEntryLabel>
<PubMyUiDocEntryField>
<Input />
</PubMyUiDocEntryField>
</PubMyUiDocEntryCell>
<PubMyUiDocEntryCell>
<PubMyUiDocEntryLabel>DPJP</PubMyUiDocEntryLabel>
<PubMyUiDocEntryField>
<Input />
</PubMyUiDocEntryField>
</PubMyUiDocEntryCell>
<PubMyUiDocEntryCell />
<PubMyUiDocEntryCell>
<PubMyUiDocEntryLabel>Status</PubMyUiDocEntryLabel>
<PubMyUiDocEntryField>
<Input />
</PubMyUiDocEntryField>
</PubMyUiDocEntryCell>
</PubMyUiDocEntryBlock>
<div class="md:grid md:grid-cols-2 font-semibold">
<div class="md:pe-10">
<PubMyUiDocEntryBlock>
<PubMyUiDocEntryCell>
<PubMyUiDocEntryLabel>Tgl Order</PubMyUiDocEntryLabel>
<PubMyUiDocEntryField>
<Input />
</PubMyUiDocEntryField>
</PubMyUiDocEntryCell>
<PubMyUiDocEntryCell>
<PubMyUiDocEntryLabel>Status</PubMyUiDocEntryLabel>
<PubMyUiDocEntryField>
<Input />
</PubMyUiDocEntryField>
</PubMyUiDocEntryCell>
</PubMyUiDocEntryBlock>
</div>
<div class="md:ps-10">
<PubMyUiDocEntryBlock>
<PubMyUiDocEntryCell>
<PubMyUiDocEntryLabel position="dynamic">DPJP</PubMyUiDocEntryLabel>
<PubMyUiDocEntryField>
<Input />
</PubMyUiDocEntryField>
</PubMyUiDocEntryCell>
<PubMyUiDocEntryCell>
<PubMyUiDocEntryLabel position="dynamic">PPDS</PubMyUiDocEntryLabel>
<PubMyUiDocEntryField>
<Input />
</PubMyUiDocEntryField>
</PubMyUiDocEntryCell>
</PubMyUiDocEntryBlock>
</div>
</div>
</template>
@@ -0,0 +1,88 @@
<script setup lang="ts">
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
import type { Prescription } from '~/models/prescription'
import * as DE from '~/components/pub/my-ui/doc-entry';
import PrescriptionItemList from '~/components/app/prescription-item/list-entry.vue';
interface Props {
data: Prescription[]
isLoading: boolean
paginationMeta?: PaginationMeta
}
defineProps<Props>()
</script>
<template>
<div v-if="isLoading" class="p-10 text-center">
Memuat data..
</div>
<div v-else-if="data && data.length == 0" class="p-10 text-center">
<div class="mb-4 xl:mb-5">Belum Ada Data</div>
<!-- <div>
<Button>
<Icon name="i-lucide-plus" class="me-2 align-middle" />
Tambah Order
</Button>
</div> -->
</div>
<div v-else v-for="(item, idx) in data">
<Separator class="my-5" />
<div class="md:grid md:grid-cols-2 font-semibold">
<div>
<DE.Block mode="preview">
<DE.Cell>
<DE.Label>Order #{{ data.length - idx }}</DE.Label>
<DE.Field>
2025-01-01
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label>Status</DE.Label>
<DE.Field>
{{ item.status_code }}
</DE.Field>
</DE.Cell>
</DE.Block>
</div>
<div>
<DE.Block mode="preview">
<DE.Cell>
<DE.Label>DPJP</DE.Label>
<DE.Field>
{{ item.doctor?.employee?.person.name }}
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label>PPDS</DE.Label>
<DE.Field>
{{ item.specialistIntern?.person.name }}
</DE.Field>
</DE.Cell>
</DE.Block>
</div>
</div>
<PrescriptionItemList :data="item.items || []" />
</div>
</template>
<!-- <Separator class="my-4 xl:my-5" />
<AppPrescriptionEntry />
<div class="flex content-center mb-3">
<div class="me-auto pt-2">
<div class="font-semibold md:text-sm xl:text-base">Daftar Obat</div>
</div>
<div>
<Button @click="addMedicine" class="me-2">
<Icon name="i-lucide-plus" />
Tambah Non Racikan
</Button>
<Button @click="addMedicineMix">
<Icon name="i-lucide-plus" />
Tambah Racikan
</Button>
</div>
</div>
<PrescriptionItemListEntry :data=[] /> -->
@@ -0,0 +1,56 @@
<script setup lang="ts">
// import { Block, Cell } from '~/components/pub/my-ui/doc-entry/index'
// import Block from '~/components/pub/my-ui/doc-entry/block.vue'
// import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
</script>
<template>
<div class="p-10 text-center">
<div class="mb-4 xl:mb-5">Belum Ada Data</div>
<div>
<Button>
<Icon name="i-lucide-plus" class="me-2 align-middle" />
Tambah Order
</Button>
</div>
</div>
<Separator class="my-5" />
<div class="md:grid md:grid-cols-2 font-semibold">
<div>
<PubCustomUiDocEntryBlock mode="preview">
<PubCustomUiDocEntryCell>
<PubCustomUiDocEntryLabel>Order #1</PubCustomUiDocEntryLabel>
<PubCustomUiDocEntryColon />
<PubCustomUiDocEntryField>
2025-01-01
</PubCustomUiDocEntryField>
</PubCustomUiDocEntryCell>
<PubCustomUiDocEntryCell>
<PubCustomUiDocEntryLabel>Status</PubCustomUiDocEntryLabel>
<PubCustomUiDocEntryColon />
<PubCustomUiDocEntryField>
Status
</PubCustomUiDocEntryField>
</PubCustomUiDocEntryCell>
</PubCustomUiDocEntryBlock>
</div>
<div>
<PubCustomUiDocEntryBlock mode="preview">
<PubCustomUiDocEntryCell>
<PubCustomUiDocEntryLabel>DPJP</PubCustomUiDocEntryLabel>
<PubCustomUiDocEntryColon />
<PubCustomUiDocEntryField>
Nama Dokter
</PubCustomUiDocEntryField>
</PubCustomUiDocEntryCell>
<PubCustomUiDocEntryCell>
<PubCustomUiDocEntryLabel>PPDS</PubCustomUiDocEntryLabel>
<PubCustomUiDocEntryColon />
<PubCustomUiDocEntryField>
Nama PPDS
</PubCustomUiDocEntryField>
</PubCustomUiDocEntryCell>
</PubCustomUiDocEntryBlock>
</div>
</div>
</template>
+62 -32
View File
@@ -1,5 +1,37 @@
<script setup lang="ts">
import * as DE from '~/components/pub/my-ui/doc-entry';
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
import Nav from '~/components/pub/my-ui/nav-footer/ca-ed-su.vue'
import type { Prescription } from '~/models/prescription';
import PrescriptionItem from '~/components/app/prescription-item/list.vue';
import { add } from 'date-fns';
interface Props {
data: Prescription[]
paginationMeta: PaginationMeta
}
const props = defineProps<Props>()
const emit = defineEmits<{
cancel: [data: any]
edit: [data: any],
submit: [data: any]
}>()
function navClick(type: 'cancel' | 'edit' | 'submit', data: Prescription): void {
if (type === 'cancel') {
emit('cancel', data)
} else if (type === 'edit') {
emit('edit', data)
} else if (type === 'submit') {
emit('submit', data)
}
}
</script>
<template>
<div class="p-10 text-center">
<div v-if="data.length == 0" class="p-10 text-center">
<div class="mb-4 xl:mb-5">Belum Ada Data</div>
<div>
<Button>
@@ -8,35 +40,33 @@
</Button>
</div>
</div>
<Separator class="my-5" />
<div>
<PubMyUiDocEntryBlock mode="preview" :colCount=3>
<PubMyUiDocEntryCell>
<PubMyUiDocEntryLabel>DPJP</PubMyUiDocEntryLabel>
<PubMyUiDocEntryField>
<Input />
</PubMyUiDocEntryField>
</PubMyUiDocEntryCell>
<PubMyUiDocEntryCell />
<PubMyUiDocEntryCell>
<PubMyUiDocEntryLabel>Tgl Order</PubMyUiDocEntryLabel>
<PubMyUiDocEntryField>
<Input />
</PubMyUiDocEntryField>
</PubMyUiDocEntryCell>
<PubMyUiDocEntryCell>
<PubMyUiDocEntryLabel>DPJP</PubMyUiDocEntryLabel>
<PubMyUiDocEntryField>
<Input />
</PubMyUiDocEntryField>
</PubMyUiDocEntryCell>
<PubMyUiDocEntryCell />
<PubMyUiDocEntryCell>
<PubMyUiDocEntryLabel>Status</PubMyUiDocEntryLabel>
<PubMyUiDocEntryField>
<Input />
</PubMyUiDocEntryField>
</PubMyUiDocEntryCell>
</PubMyUiDocEntryBlock>
</div>
<template v-for="item, idx in data">
<div :class="'text-sm 2xl:text-base font-semibold ' + (item.status_code == 'new' ? 'mb-2' : 'mb-2')">
Order #{{ data.length - idx }} - {{ item.issuedAt?.substring(0, 10) || item.createdAt?.substring(0, 10) }} - {{ item.status_code }}
</div>
<DE.Block mode="preview" :col-count="7" class="!mb-3">
<DE.Cell :col-span="3">
<DE.Label :class="'font-semibold ' + (item.status_code == 'new' ? 'pt-2' : '')">DPJP</DE.Label>
<DE.Field :class="item.status_code == 'new' ? 'pt-2' : ''">
{{ item.doctor?.employee?.person?.name || '-' }}
</DE.Field>
</DE.Cell>
<DE.Cell :col-span="3">
<DE.Label :class="'font-semibold ' + (item.status_code == 'new' ? 'pt-2' : '')">PPDS</DE.Label>
<DE.Field :class="item.status_code == 'new' ? 'pt-2' : ''">
...........
</DE.Field>
</DE.Cell>
<div class="flex justify-end" >
<Nav
v-if="item.status_code == 'new'"
:small-mode="true"
:default-class="'flex gap-1'"
@click="(type) => { navClick(type, item) }"
/>
</div>
</DE.Block>
<PrescriptionItem :data="item.items || []" @click="console.log('click')" class="mb-10" />
<!-- <div v-if="idx < data.length - 1" class="my-8 -mx-4 border-t border-t-slate-300" /> -->
</template>
</template>