init: treatment report
feat(treatment-report): add treatment report component with sample data Implement new treatment report feature including list view component, sample data, and configuration. The component supports pagination, filtering by date range, and search functionality. Also integrates with encounter process and home views. wip: init form and schema
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { SelectDPJP } from './fields'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SelectDPJP />
|
||||
</template>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as SelectDPJP } from './select-dpjp.vue'
|
||||
@@ -0,0 +1,3 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>select dpjp</template>
|
||||
@@ -0,0 +1,88 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import { format } from 'date-fns'
|
||||
import { id } from 'date-fns/locale'
|
||||
|
||||
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
|
||||
import type { TreatmentReportData } from '~/components/app/treatment-report/sample'
|
||||
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: 50 },
|
||||
],
|
||||
|
||||
headers: [
|
||||
[
|
||||
{ label: 'TANGGAL LAPORAN' },
|
||||
{ label: 'DPJP' },
|
||||
{ label: 'OPERATOR' },
|
||||
{ label: 'TANGGAL PEMBEDAHAN' },
|
||||
{ label: 'JENIS OPERASI' },
|
||||
{ label: 'KODE BILLING' },
|
||||
{ label: 'SISTEM OPERASI' },
|
||||
{ label: 'AKSI' },
|
||||
],
|
||||
],
|
||||
|
||||
keys: ['reportAt', 'dpjp', 'operator', 'operationAt', 'operationType', 'billing', 'system', 'action'],
|
||||
|
||||
delKeyNames: [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
],
|
||||
|
||||
parses: {
|
||||
reportAt: (rec: unknown): unknown => {
|
||||
const attr = (rec as TreatmentReportData).reportAt
|
||||
const result = format(new Date(attr), 'd MMMM yyyy, HH:mm', { locale: id })
|
||||
|
||||
return result
|
||||
},
|
||||
operationAt: (rec: unknown): unknown => {
|
||||
const attr = (rec as TreatmentReportData).operationAt
|
||||
const result = format(new Date(attr), 'd MMMM yyyy', { locale: id })
|
||||
|
||||
return result
|
||||
},
|
||||
system: (rec: unknown): unknown => {
|
||||
return 'Cito'
|
||||
},
|
||||
operator: (rec: unknown): unknown => {
|
||||
return 'dr. Dewi Arum Sawitri, Sp.An'
|
||||
},
|
||||
billing: (rec: unknown): unknown => {
|
||||
return 'General'
|
||||
},
|
||||
operationType: (rec: unknown): unknown => {
|
||||
return 'Besar'
|
||||
},
|
||||
dpjp: (rec: unknown): unknown => {
|
||||
return 'dr. Irwansyah Kurniawan Sp.Bo'
|
||||
},
|
||||
parent: (rec: unknown): unknown => {
|
||||
const recX = rec as any
|
||||
return recX.parent?.name || '-'
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
}
|
||||
return res
|
||||
},
|
||||
},
|
||||
|
||||
htmls: {},
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<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>
|
||||
@@ -0,0 +1,54 @@
|
||||
import { addWeeks, formatISO } from 'date-fns'
|
||||
|
||||
export type TreatmentReportData = {
|
||||
id: number
|
||||
reportAt: string
|
||||
operationAt: 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: TreatmentReportData[] = [
|
||||
{
|
||||
id: 1,
|
||||
reportAt: formatISO(addWeeks(new Date(), -1)),
|
||||
operationAt: formatISO(addWeeks(new Date(), 1)),
|
||||
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,
|
||||
reportAt: new Date().toISOString(),
|
||||
operationAt: formatISO(addWeeks(new Date(), 2)),
|
||||
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
|
||||
]
|
||||
@@ -130,7 +130,6 @@ const tabsRaws: TabItem[] = [
|
||||
component: MedicineProtocolList,
|
||||
props: { data: protocolRows, paginationMeta },
|
||||
},
|
||||
{ value: 'report', label: 'Laporan Tindakan', groups: ['chemotherapy'] },
|
||||
{ value: 'patient-note', label: 'CPRJ', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{
|
||||
value: 'education-assessment',
|
||||
@@ -162,13 +161,20 @@ const tabsRaws: TabItem[] = [
|
||||
{ value: 'resume', label: 'Resume', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{ value: 'control', label: 'Surat Kontrol', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{ value: 'screening', label: 'Skrinning MPP', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{
|
||||
value: 'report',
|
||||
label: 'Laporan Tindakan',
|
||||
groups: ['ambulatory', 'rehabilitation', 'chemotherapy'],
|
||||
component: Consultation,
|
||||
props: { encounter: data },
|
||||
},
|
||||
{ value: 'supporting-document', label: 'Upload Dokumen Pendukung', groups: ['ambulatory', 'rehabilitation'] },
|
||||
{ value: 'price-list', label: 'Tarif Tindakan', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
]
|
||||
|
||||
const tabs = computed(() => {
|
||||
return tabsRaws
|
||||
.filter((tab: TabItem) => tab.groups ? tab.groups.some((group: string) => props.classes?.includes(group)) : true)
|
||||
.filter((tab: TabItem) => (tab.groups ? tab.groups.some((group: string) => props.classes?.includes(group)) : true))
|
||||
.map((tab: TabItem) => {
|
||||
return { ...tab, props: { ...tab.props, encounter: data } }
|
||||
})
|
||||
|
||||
@@ -22,6 +22,7 @@ import CpLabOrder from '~/components/content/cp-lab-order/main.vue'
|
||||
import Radiology from '~/components/content/radiology-order/main.vue'
|
||||
import Consultation from '~/components/content/consultation/list.vue'
|
||||
import Cprj from '~/components/content/cprj/entry.vue'
|
||||
import TreatmentReport from '~/components/content/treatment-report/list.vue'
|
||||
import DocUploadList from '~/components/content/document-upload/list.vue'
|
||||
import GeneralConsentList from '~/components/content/general-consent/entry.vue'
|
||||
import ResumeList from '~/components/content/resume/list.vue'
|
||||
@@ -90,6 +91,13 @@ const tabs: TabItem[] = [
|
||||
{ value: 'resume', label: 'Resume', component: ResumeList, props: { encounter: data } },
|
||||
{ value: 'control', label: 'Surat Kontrol', component: ControlLetterList, props: { encounter: data } },
|
||||
{ value: 'screening', label: 'Skrinning MPP' },
|
||||
{
|
||||
value: 'report',
|
||||
label: 'Laporan Tindakan',
|
||||
groups: ['ambulatory', 'rehabilitation', 'chemotherapy'],
|
||||
component: TreatmentReport,
|
||||
props: { encounter: data },
|
||||
},
|
||||
{
|
||||
value: 'supporting-document',
|
||||
label: 'Upload Dokumen Pendukung',
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import AppTreatmentReportEntry from '~/components/app/treatment-report/entry-form.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppTreatmentReportEntry />
|
||||
</template>
|
||||
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
// Components
|
||||
import AppTreatmentReportList from '~/components/app/treatment-report/list.vue'
|
||||
|
||||
// Samples
|
||||
import { sampleRows, type TreatmentReportData } from '~/components/app/treatment-report/sample'
|
||||
|
||||
const search = ref('')
|
||||
const dateFrom = ref('')
|
||||
const dateTo = ref('')
|
||||
|
||||
// filter + pencarian sederhana (client-side)
|
||||
const filtered = computed(() => {
|
||||
const q = search.value.trim().toLowerCase()
|
||||
return sampleRows.filter((r: TreatmentReportData) => {
|
||||
if (q) {
|
||||
return r.nama.toLowerCase().includes(q) || r.noRm.toLowerCase().includes(q) || r.dokter.toLowerCase().includes(q)
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto max-w-full">
|
||||
<div class="border-b p-6">
|
||||
<h1 class="text-2xl font-semibold">Laporan Tindakan</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Infomasi laporan tindakan pasien</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3 border-b p-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="search"
|
||||
placeholder="Cari Nama / No.RM"
|
||||
class="w-64 rounded border px-3 py-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="dateFrom"
|
||||
type="date"
|
||||
class="rounded border px-3 py-2"
|
||||
/>
|
||||
<span class="text-sm text-gray-500">-</span>
|
||||
<input
|
||||
v-model="dateTo"
|
||||
type="date"
|
||||
class="rounded border px-3 py-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button class="ml-auto rounded bg-orange-500 px-3 py-2 text-white hover:bg-orange-600">Filter</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto p-4">
|
||||
<AppTreatmentReportList
|
||||
:data="filtered"
|
||||
:pagination-meta="{
|
||||
recordCount: 2,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalPage: 1,
|
||||
hasPrev: false,
|
||||
hasNext: false,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import type { PagePermission } from '~/models/role'
|
||||
import Error from '~/components/pub/my-ui/error/error.vue'
|
||||
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['rbac'],
|
||||
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
||||
title: 'Tambah Laporan',
|
||||
contentFrame: 'cf-full-width',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
useHead({
|
||||
title: () => route.meta.title as string,
|
||||
})
|
||||
|
||||
const roleAccess: PagePermission = PAGE_PERMISSIONS['/patient']
|
||||
|
||||
const { checkRole, hasReadAccess } = useRBAC()
|
||||
|
||||
// Check if user has access to this page
|
||||
// const hasAccess = checkRole(roleAccess)
|
||||
const hasAccess = true
|
||||
if (!hasAccess) {
|
||||
navigateTo('/403')
|
||||
}
|
||||
|
||||
// Define permission-based computed properties
|
||||
// const canRead = hasReadAccess(roleAccess)
|
||||
const canRead = true
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="canRead">
|
||||
<ContentTreatmentReportAdd />
|
||||
</div>
|
||||
<Error
|
||||
v-else
|
||||
:status-code="403"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,89 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const isoDateTime = z.string().min(1, 'Tanggal / waktu wajib diisi')
|
||||
const positiveInt = z.number().int().nonnegative()
|
||||
|
||||
const OperatorTeamSchema = z.object({
|
||||
dpjpId: z.number().int(),
|
||||
operatorId: z.number().int(),
|
||||
assistantOperatorId: z.number().int().optional().nullable(),
|
||||
instrumentNurseId: z.number().int().optional().nullable(),
|
||||
|
||||
surgeryDate: isoDateTime,
|
||||
actionDiagnosis: z.string().min(1),
|
||||
|
||||
postOpNurseId: z.number().int().optional().nullable(),
|
||||
})
|
||||
|
||||
const ProcedureSchema = z.object({
|
||||
id: z.number().int().optional(),
|
||||
name: z.string().min(1),
|
||||
code: z.string().min(1),
|
||||
})
|
||||
|
||||
const OperationExecutionSchema = z.object({
|
||||
surgeryType: z.enum(['kecil', 'sedang', 'besar', 'khusus']),
|
||||
billingCode: z.string().min(1),
|
||||
operationSystem: z.enum(['khusus', 'cito', 'elektif']),
|
||||
|
||||
operationStartAt: isoDateTime,
|
||||
operationEndAt: isoDateTime,
|
||||
|
||||
anesthesiaStartAt: isoDateTime,
|
||||
anesthesiaEndAt: isoDateTime,
|
||||
|
||||
surgeryCleanType: z.enum(['bersih', 'bersih_terkontaminasi', 'terkontaminasi', 'kotor']).optional(),
|
||||
surgeryNumber: z.enum(['1', '2', '3', '4+', 'tidak_diketahui']).optional(),
|
||||
|
||||
birthPlaceNote: z.string().optional(),
|
||||
babyWeightGram: positiveInt.optional(),
|
||||
birthCondition: z.string().optional(),
|
||||
|
||||
operationDescription: z.string().min(1),
|
||||
|
||||
bleedingAmountCc: positiveInt.optional(),
|
||||
|
||||
birthRemark: z.enum(['lahir_hidup', 'lahir_mati', 'abortus', 'tidak_diketahui']).optional(),
|
||||
})
|
||||
|
||||
const BloodComponentSchema = z.object({
|
||||
used: z.boolean().default(false),
|
||||
volumeCc: positiveInt.optional(),
|
||||
})
|
||||
|
||||
const BloodInputSchema = z.object({
|
||||
prc: BloodComponentSchema,
|
||||
ffp: BloodComponentSchema,
|
||||
wb: BloodComponentSchema,
|
||||
tc: BloodComponentSchema,
|
||||
})
|
||||
|
||||
const ImplantSchema = z.object({
|
||||
brand: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
stickerNumber: z.string().optional(),
|
||||
companionName: z.string().optional(),
|
||||
})
|
||||
|
||||
const SpecimenSchema = z.object({
|
||||
destination: z.string().min(1),
|
||||
})
|
||||
|
||||
const TissueNoteSchema = z.object({
|
||||
note: z.string().min(1),
|
||||
})
|
||||
|
||||
export const ActionReportSchema = z.object({
|
||||
operatorTeam: OperatorTeamSchema,
|
||||
procedures: z.array(ProcedureSchema).min(1),
|
||||
|
||||
operationExecution: OperationExecutionSchema,
|
||||
|
||||
bloodInput: BloodInputSchema.optional(),
|
||||
implant: ImplantSchema.optional(),
|
||||
specimen: SpecimenSchema.optional(),
|
||||
|
||||
tissueNotes: z.array(TissueNoteSchema).optional(),
|
||||
})
|
||||
|
||||
export type ActionReportFormData = z.infer<typeof ActionReportSchema>
|
||||
Reference in New Issue
Block a user