Feat: UI Laporan Operasi

This commit is contained in:
hasyim_kai
2025-11-25 14:39:41 +07:00
parent f6ae61849d
commit 39b778ab78
19 changed files with 756 additions and 211 deletions
@@ -0,0 +1,21 @@
<script setup lang="ts">
import { ActionEvents, type ListItemDto } from '~/components/pub/my-ui/data/types';
import Button from '~/components/pub/ui/button/Button.vue';
const props = defineProps<{
}>()
const isModalOpen = inject<Ref<boolean>>('isHistoryDialogOpen')!
function openDialog() {
isModalOpen.value = true
}
</script>
<template>
<Button type="button" variant="outline" class="text-orange-500 border border-orange-400 bg-orange-50"
@click="openDialog">
<Icon name="i-lucide-history" class="h-4 w-4 align-middle transition-colors" />
History
</Button>
</template>
@@ -0,0 +1,73 @@
<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 { cn } from '~/lib/utils'
interface InstallationFormData {
name: string
code: string
encounterClassCode: string
}
const props = defineProps<{
schema: any
initialValues?: Partial<InstallationFormData>
}>()
const emit = defineEmits<{
submit: [values: InstallationFormData, resetForm: () => void]
reset: [resetForm: () => void]
}>()
// Form submission handler
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
emit('submit', values, resetForm)
}
// Form cancel handler
function onResetForm({ resetForm }: { resetForm: () => void }) {
emit('reset', resetForm)
}
</script>
<template>
<Form
v-slot="{ handleSubmit, resetForm }"
as=""
keep-values
: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">
<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"
/>
</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,52 @@
<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, updatedBy: 'Kakek Sugiono', createdAt: new Date(Date.now() - 86400000 * 2) },
{ id: 2, updatedBy: 'Kakek Sugiono', createdAt: new Date(Date.now() - 86400000) },
{ id: 3, updatedBy: 'Kakek Sugiono', 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-14 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('mt-0 text-base 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', '')">Ditambahkan Oleh : {{ item.updatedBy }}</h1>
<NuxtLink class="mt-1 text-orange-500" :to="`surgery-report/${item.id}`">
Lihat Detail
</NuxtLink>
</div>
</div>
</li>
</ul>
</template>
@@ -0,0 +1,94 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import { Label as RadioLabel } from '~/components/pub/ui/label'
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
import { cn } from '~/lib/utils'
import * as DE from '~/components/pub/my-ui/doc-entry'
const props = defineProps<{
fieldName?: string
label?: string
errors?: FormErrors
class?: string
radioGroupClass?: string
radioItemClass?: string
labelClass?: string
isRequired?: boolean
}>()
const {
fieldName = 'isNewBorn',
label = 'Status Pasien',
errors,
class: containerClass,
radioGroupClass,
radioItemClass,
labelClass,
} = props
const newbornOptions = [
{ label: 'PRC', value: 'prc' },
{ label: 'FFP', value: 'ffp' },
{ label: 'WB', value: 'wb' },
{ label: 'TC', value: 'tc' },
]
</script>
<template>
<DE.Cell :class="cn('radio-group-field', containerClass)" :col-span="2">
<DE.Label
:label-for="fieldName"
:is-required="isRequired"
>
{{ label }}
</DE.Label>
<DE.Field
:id="fieldName"
:errors="errors"
>
<FormField
v-slot="{ componentField }"
:name="fieldName"
>
<FormItem>
<FormControl>
<RadioGroup
v-bind="componentField"
:class="cn('flex flex-row flex-wrap gap-4 sm:gap-6', radioGroupClass)"
>
<div
v-for="(option, index) in newbornOptions"
:key="option.value"
:class="cn('flex min-w-fit items-center space-x-2 pt-1', radioItemClass)"
>
<RadioGroupItem
:id="`${fieldName}-${index}`"
:value="option.value"
:class="
cn(
'relative h-4 w-4 rounded-full border-muted-foreground before:absolute before:inset-1 before:rounded-full before:bg-primary before:opacity-0 before:transition-opacity data-[state=checked]:border-primary data-[state=checked]:bg-white data-[state=checked]:before:opacity-100 sm:h-5 sm:w-5',
containerClass,
)
"
/>
<RadioLabel
:for="`${fieldName}-${index}`"
:class="
cn(
'cursor-pointer select-none text-xs font-normal leading-none transition-colors hover:text-primary peer-disabled:cursor-not-allowed peer-disabled:opacity-70 sm:text-sm',
labelClass,
)
"
>
{{ option.label }}
</RadioLabel>
</div>
</RadioGroup>
</FormControl>
<FormMessage class="ml-0 mt-1" />
</FormItem>
</FormField>
</DE.Field>
</DE.Cell>
</template>
+104 -14
View File
@@ -1,7 +1,9 @@
<script setup lang="ts">
import DetailRow from '~/components/pub/my-ui/form/view/detail-row.vue'
import { type Variants, Badge } from '~/components/pub/ui/badge'
import { cn, } from '~/lib/utils'
import type { ControlLetter } from '~/models/control-letter'
import * as DE from '~/components/pub/my-ui/doc-entry'
// #region Props & Emits
const props = defineProps<{
@@ -9,9 +11,18 @@ const props = defineProps<{
}>()
const emit = defineEmits<{
(e: 'click', type: string): void
(e: 'click'): void
}>()
const dummy = [
{
id: 1,
number: 1,
name: 'Operasi',
code: 'OP-001'
}
]
// #endregion
// #region State & Computed
@@ -28,8 +39,8 @@ const emit = defineEmits<{
// #endregion region
// #region Utilities & event handlers
function onClick(type: string) {
emit('click', type)
function onClick() {
emit('click')
}
// #endregion
@@ -38,17 +49,96 @@ function onClick(type: string) {
</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>
<article :class="cn('space-y-10',)">
<div :class="cn('space-y-2',)">
<h1>Dibuat oleh {{ `Dr. Agus` }} pada {{ `11 Agustus 2025, 20.00` }}</h1>
<DetailRow label="Tanggal Laporan">{{ props.instance?.date ? new
Date(props.instance?.date).toLocaleDateString('id-ID') : '-' }}</DetailRow>
</div>
<div :class="cn('space-y-2',)">
<h1 class="font-semibold text-lg">Tim Pelaksanaan Operasi</h1>
<DetailRow label="DPJP Bedah">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Operator">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Asisten Operator">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Instrumentir">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="DPJP Anastesi">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Perawat Anastesi">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Tanggal Pembedahan">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Diagnosa Pra Bedah">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Perawat Pasca Bedah">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
</div>
<div :class="cn('space-y-2',)">
<h1 class="font-semibold text-lg">Tindakan Operatif / Non Operatif Lain</h1>
<div class="border border-gray-200 rounded-lg overflow-hidden">
<Table>
<TableHeader class="bg-gray-100">
<TableRow>
<TableHead class="w-14">No</TableHead>
<TableHead class="">Prosedur</TableHead>
<TableHead class="">Code</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-for="(field, idx) in dummy" :key="idx">
<TableCell class="">{{ idx + 1 }}</TableCell>
<TableCell class="">{{ field.name }}</TableCell>
<TableCell class="">{{ field.code }}</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</div>
<div :class="cn('space-y-2',)">
<h1 class="font-semibold text-lg">Data Pelaksanaan Operasi</h1>
<DE.Block :col-count="2" :cell-flex="false">
<div>
<DetailRow label="Jenis Operasi">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Kode Biling">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Sitem Operasi">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Operasi Mulai">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Operasi Selesai">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Lama Operasi"><b>{{ 1 }}</b> Jam <b>{{ 1 }}</b> Menit</DetailRow>
<DetailRow label="Pembiusan Mulai">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Pembiusan Selesai">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Lama Pembiusan"><b>{{ 1 }}</b> Jam <b>{{ 1 }}</b> Menit</DetailRow>
</div>
<div>
<DetailRow label="Jenis Pembedahan">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Operasi ke">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Keterangan Lahir">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Ket Tempat Lahir">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Berat Badan">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Ket Saat Lahir">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Uraian Operasi">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Jumlah Pendarahan">{{ props.instance?.date ? props.instance?.date : '-' }} CC</DetailRow>
</div>
<div>
<DetailRow label="PRC">{{ props.instance?.date ? props.instance?.date : '-' }} CC</DetailRow>
<DetailRow label="FPP">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="WB">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="TC">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Merk">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Nama Implant">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Stiker/Nomor Register Implant">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Nama Pendamping Implant">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
</div>
<div>
<DetailRow label-class="w-96" label="Specimen/Jaringan dikirim ke">{{ props.instance?.date ? props.instance?.date : '-' }}</DetailRow>
<DetailRow label="Keterangan Jaringan">
<Badge :variant="`outline`">
{{ `Example` }}
</Badge>
</DetailRow>
</div>
</DE.Block>
</div>
<div class="border-t-1 my-2 flex justify-end border-t-slate-300 py-2">
<PubMyUiNavFooterBa @click="onClick" />
</div>
</article>
</template>
<style scoped></style>
+91 -15
View File
@@ -4,6 +4,7 @@ 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 { FieldArray } from 'vee-validate'
import * as DE from '~/components/pub/my-ui/doc-entry'
import SelectDpjpBedah from './_common/select-dpjp-bedah.vue'
@@ -17,16 +18,18 @@ import SelectBirthDesc from './_common/select-birth-desc.vue'
import SelectBirthPlaceDesc from './_common/select-birth-place-desc.vue'
import SelectSpecimenType from './_common/select-specimen-type.vue'
import TextAreaInput from '~/components/pub/my-ui/form/text-area-input.vue'
import RadioBloodType from './_common/radio-blood-type.vue'
import OperativeActionPicker from './operative-action/picker-dialog.vue'
const props = defineProps<{
schema: any
initialValues?: any
errors?: FormErrors
operativeActionList?: any[]
}>()
const formSchema = toTypedSchema(props.schema)
const formRef = ref()
const isOperativeActionDialogOpen = inject<Ref<boolean>>('isOperativeActionDialogOpen')!
defineExpose({
validate: () => formRef.value?.validate(),
@@ -34,10 +37,6 @@ defineExpose({
setValues: (values: any, shouldValidate = true) => formRef.value?.setValues(values, shouldValidate),
values: computed(() => formRef.value?.values),
})
const handleToggleOperativeActionDialog = () => {
isOperativeActionDialogOpen.value = !isOperativeActionDialogOpen.value
}
</script>
<template>
@@ -95,18 +94,20 @@ const handleToggleOperativeActionDialog = () => {
</DE.Cell>
<InputBase :errors="errors"
field-name="a9"
label="Perawat Pra Bedah" placeholder="Isi Perawat Pra Bedah"
label="Perawat Pasca Bedah" placeholder="Isi Perawat Pasca Bedah"
/>
</DE.Block>
<!-- PICKER -->
<DE.Block :col-count="1" :cell-flex="false" class="w-1/2">
<OperativeActionPicker field-name="a63" title="Prosedur" />
</DE.Block>
<!-- -->
<DE.Block :col-count="3" :cell-flex="false">
<SelectSurgeryType :errors="errors"
field-name="a10"
label="Tipe Operasi" placeholder="Pilih Tipe Operasi"
label="Jenis Operasi" placeholder="Pilih Jenis Operasi"
is-required
/>
<SelectBillingCode :errors="errors"
@@ -133,32 +134,32 @@ const handleToggleOperativeActionDialog = () => {
/>
<div>
<p class="mt-1.5 mb-2">Lama Operasi</p>
<h1 class="text-lg"><b>{{ 1 }}</b> Jam <b>{{ 1 }}</b> Menit</h1>
<h1 class="text-base"><b>{{ 1 }}</b> Jam <b>{{ 1 }}</b> Menit</h1>
</div>
<SelectDate :errors="errors"
field-name="a7"
field-name="a44"
label="Pembiusan Mulai"
is-required
is-with-time
/>
<SelectDate :errors="errors"
field-name="a7"
field-name="a45"
label="Pembiusan Selesai"
is-required
is-with-time
/>
<div>
<p class="mt-1.5 mb-2">Lama Pembiusan</p>
<h1 class="text-lg"><b>{{ 1 }}</b> Jam <b>{{ 1 }}</b> Menit</h1>
<h1 class="text-base"><b>{{ 1 }}</b> Jam <b>{{ 1 }}</b> Menit</h1>
</div>
<SelectDissectionType :errors="errors"
field-name="a13"
label="Tipe Pembedahan" placeholder="Pilih Tipe Pembedahan"
label="Jenis Pembedahan" placeholder="Pilih Jenis Pembedahan"
is-required
/>
<SelectSurgeryOrder :errors="errors"
field-name="a14"
label="Urutan Operasi" placeholder="Pilih Urutan Operasi"
label="Urutan Operasi Ke" placeholder="Pilih Urutan Operasi Ke"
is-required
/>
<SelectBirthDesc :errors="errors"
@@ -171,13 +172,88 @@ const handleToggleOperativeActionDialog = () => {
label="Ket. Tempat Lahir" placeholder="Pilih Ket. Tempat Lahir"
is-required
/>
<InputBase :errors="errors"
field-name="a6"
label="Berat Badan" placeholder="Isi Berat Badan"
right-label="Gram"
numeric-only
/>
<InputBase :errors="errors"
field-name="a6"
label="Ket. Saat Lahir" placeholder="Isi Ket. Saat Lahir"
/>
<TextAreaInput :errors="errors"
field-name="a8"
label="Uraian Operasi" placeholder="Isi Uraian Operasi"
is-required
/>
<InputBase :errors="errors"
field-name="a6"
label="Jumlah Pendarahan" placeholder="Isi Jumlah Pendarahan"
right-label="cc"
numeric-only
/>
</DE.Block>
<DE.Block :col-count="1" :cell-flex="false" class="w-1/2">
<RadioBloodType :errors="errors"
field-name="a16"
label="Jenis Darah Masuk" placeholder="Pilih Jenis Darah Masuk"
/>
<InputBase :errors="errors"
class=""
field-name="a6"
label="Jumlah Darah Masuk" placeholder="Isi Jumlah Darah Masuk"
right-label="cc"
numeric-only
/>
</DE.Block>
<DE.Block :col-count="3" :cell-flex="false">
<InputBase :errors="errors"
field-name="a6"
label="Merk" placeholder="Isi Merk"
/>
<InputBase :errors="errors"
field-name="a6"
label="Nama Implant" placeholder="Isi Nama Implant"
/>
<InputBase :errors="errors"
field-name="a6"
label="Stiker / Nomor Register Implant" placeholder="Isi Stiker / Nomor Register Implant"
/>
<InputBase :errors="errors"
field-name="a6"
label="Nama Penedamping Implant" placeholder="Isi Nama Penedamping Implant"
/>
<SelectSpecimenType :errors="errors"
field-name="a17"
label="Specimen/Jaringan Dikirim Ke" placeholder="Pilih Specimen/Jaringan Dikirim Ke"
is-required
/>
</DE.Block>
<DE.Block :col-count="1" :cell-flex="false" class="w-1/2">
<FieldArray v-slot="{ fields, push, remove }" name="a32">
<div v-for="(field, idx) in fields" :key="idx" class="flex items-end gap-3 mb-3">
<InputBase :errors="errors"
:field-name="`a32[${idx}]`"
label="Keterangan Jaringan" placeholder="Isi Keterangan Jaringan"
/>
<Button v-if="idx !== 0" type="button" variant="destructive" size="sm" @click="remove(idx)">
<Icon name="i-lucide-trash-2" class="h-4 w-4" />
</Button>
</div>
<Button type="button" variant="outline"
class="mt-3 w-full rounded-md border border-primary bg-white px-4 py-2 text-primary hover:border-primary hover:bg-primary hover:text-white sm:w-auto sm:text-sm"
@click="push(``)">
<Icon name="i-lucide-plus" class="mr-2 h-4 w-4 align-middle transition-colors" />
Tambah Keterangan Jaringan
</Button>
</FieldArray>
</DE.Block>
</Form>
</template>
@@ -1,33 +0,0 @@
<script setup lang="ts">
import { config } from './operative-action-list.cfg'
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
interface Props {
data: any[]
paginationMeta: PaginationMeta
}
defineProps<Props>()
const modelValue = defineModel<any[]>('modelValue', { default: [] })
const emit = defineEmits<{
pageChange: [page: number]
}>()
function handlePageChange(page: number) {
emit('pageChange', page)
}
</script>
<template>
<div class="space-y-4">
<PubMyUiDataTable
select-mode="multiple"
v-model="modelValue"
v-bind="config"
:rows="data"
:skeleton-size="paginationMeta?.pageSize"
/>
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
</div>
</template>
@@ -1,56 +0,0 @@
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: [{}, {}, {}, {}, {}, {}, {}, ],
headers: [
[
{ label: 'Tgl Laporan' },
{ label: 'DPJP Bedah' },
{ label: 'DPJP Anastesi' },
{ label: 'Tgl Pembedahan' },
{ label: 'Jenis Operasi' },
{ label: 'Kode Billing' },
{ label: 'Sistem Operasi' },
],
],
keys: ['date', 'doctor.employee.person.name', 'doctor.employee.person.name', 'date', 'name', 'name', 'name'],
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
},
},
components: {
// action(rec, idx) {
// return {
// idx,
// rec: rec as object,
// component: action,
// }
// },
},
htmls: {
},
}
@@ -0,0 +1,30 @@
<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>>('ap_rec_id')!
const recAction = inject<Ref<string>>('ap_rec_action')!
const recItem = inject<Ref<any>>('ap_rec_item')!
const timestamp = inject<Ref<any>>('timestamp')!
const activeKey = ref<string | null>(null)
function process() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showProcess
recItem.value = props.rec
timestamp.value = new Date().getTime()
}
</script>
<template>
<Button @click="process" variant="outline"
class="text-orange-400 border-orange-400 bg-transparent">
Pilih
<Icon name="i-lucide-arrow-right" class="h-4 w-4 align-middle transition-colors" />
</Button>
</template>
@@ -0,0 +1,44 @@
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('./dropdown-action.vue'))
export const config: Config = {
cols: [{}, {}, {width: 30}, ],
headers: [
[
{ label: 'Nama Prosedur' },
{ label: 'Code' },
{ label: 'Action' },
],
],
keys: ['name', 'code', 'action',],
delKeyNames: [
{ key: 'code', label: 'Kode' },
{ key: 'name', label: 'Nama' },
],
parses: {
},
components: {
action(rec, idx) {
return {
idx,
rec: rec as object,
component: action,
}
},
},
htmls: {
},
}
@@ -0,0 +1,84 @@
<script setup lang="ts">
// Components
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
// Helpers
import { usePaginatedList } from '~/composables/usePaginatedList'
import { toast } from '~/components/pub/ui/toast'
import { config } from './list.cfg'
// Types
import { ActionEvents, type HeaderPrep } from '~/components/pub/my-ui/data/types'
import { ProcedureSrcSchema, type ProcedureSrcFormData } from '~/schemas/procedure-src.schema'
// Handlers
import {
isReadonly,
isProcessing,
isFormEntryDialogOpen,
isRecordConfirmationOpen,
onResetState,
handleActionSave,
handleActionEdit,
handleActionRemove,
handleCancelForm,
} from '~/handlers/procedure-src.handler'
// Services
import { getList, getDetail } from '~/services/procedure-src.service'
const title = ref('')
interface Props {
data: any[]
paginationMeta: PaginationMeta
processFn: (input: unknown) => void
}
const props = defineProps<Props>()
const recId = ref<string>(``)
const recAction = ref<string>(``)
const recItem = ref<any>({})
const timestamp = ref<any>({})
provide('ap_rec_id', recId)
provide('ap_rec_action', recAction)
provide('ap_rec_item', recItem)
provide('timestamp', timestamp)
const emit = defineEmits<{
pageChange: [page: number]
toggleDialog: []
}>()
function handlePageChange(page: number) {
emit('pageChange', page)
}
watch([recId, recAction, ], () => {
switch (recAction.value) {
case ActionEvents.showProcess:
props.processFn({
id: recId.value,
code: recItem.value.code,
name: recItem.value.name,
})
emit('toggleDialog')
break
}
})
</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,116 @@
<script setup lang="ts">
import ProcedureListDialog from './list.vue'
import type { FormErrors } from '~/types/error'
import { toTypedSchema } from '@vee-validate/zod'
import { Form } from '~/components/pub/ui/form'
import { FieldArray } from 'vee-validate'
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
import * as DE from '~/components/pub/my-ui/doc-entry'
import TextAreaInput from '~/components/pub/my-ui/form/text-area-input.vue'
import { cn } from '~/lib/utils'
import TableHeader from '~/components/pub/ui/table/TableHeader.vue'
import { is } from 'date-fns/locale'
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
import List from './list.vue'
import Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
import { getList, getDetail } from '~/services/procedure-src.service'
import { ActionEvents, type HeaderPrep, type RefSearchNav } from '~/components/pub/my-ui/data/types'
interface Props {
fieldName: string
title: string
}
const props = defineProps<Props>()
const isOperativeActionDialogOpen = ref(false)
provide("isOperativeActionDialogOpen", isOperativeActionDialogOpen);
const { data, isLoading, paginationMeta, searchInput, handlePageChange, handleSearch, fetchData } = usePaginatedList({
fetchFn: (params) => getList({ ...params, includes: 'specialist,subspecialist,doctor-employee-person', }),
entityName: 'surgery-report',
})
const headerPrep: HeaderPrep = {
title: props.title,
icon: 'i-lucide-clipboard-list',
}
const refSearchNav: RefSearchNav = {
onClick: () => {
// open filter modal
},
onInput: (val: string) => {
searchInput.value = val
},
onClear: () => {
searchInput.value = ''
},
}
const handleToggleOperativeActionDialog = () => {
isOperativeActionDialogOpen.value = !isOperativeActionDialogOpen.value
}
const dummy = [
{
id: 1,
number: 1,
name: 'Operasi',
code: 'OP-001'
}
]
</script>
<template>
<div class="">
<div class="mb-2 flex justify-between">
<h1 class="mb-2 font-medium">{{ title }}</h1>
<Button @click="isOperativeActionDialogOpen = true" size="xs" variant="outline"
class="text-orange-400 border-orange-400 bg-transparent">
<Icon name="i-lucide-search" class="h-4 w-4 align-middle transition-colors" />
Pilih {{ title }}
</Button>
</div>
<FieldArray v-slot="{ fields, push, remove }" :name="props.fieldName">
<Dialog
v-model:open="isOperativeActionDialogOpen"
title="" size="xl">
<Header
:prep="headerPrep"
:ref-search-nav="refSearchNav"
v-model:search="searchInput"
@search="handleSearch" />
<!-- <List :data="dummy" :process-fn="push" /> -->
<List
:data="dummy"
:pagination-meta="paginationMeta"
@page-change="handlePageChange"
@toggle-dialog="handleToggleOperativeActionDialog"
:process-fn="push"
/>
</Dialog>
<div class="border border-gray-200 rounded-lg overflow-hidden">
<Table>
<TableHeader class="bg-gray-100">
<TableRow>
<TableHead class="">Prosedur</TableHead>
<TableHead class="">Code</TableHead>
<TableHead class="w-24">Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-for="(field, idx) in fields" :key="idx">
<TableCell class="">{{ field.value?.name }}</TableCell>
<TableCell class="">{{ field.value?.code }}</TableCell>
<TableCell class="">
<Button type="button" variant="destructive" size="sm" @click="remove(idx)">
<Icon name="i-lucide-trash-2" class="h-4 w-4" />
</Button>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</FieldArray>
</div>
</template>
@@ -32,14 +32,14 @@ const headerPrep: HeaderPrep = {
// #endregion
// #region Lifecycle Hooks
onMounted(async () => {
const result = await getDetail(controlLetterId, {
includes: "unit,specialist,subspecialist,doctor-employee-person",
})
if (result.success) {
controlLetter.value = result.body?.data
}
})
// onMounted(async () => {
// const result = await getDetail(controlLetterId, {
// includes: "unit,specialist,subspecialist,doctor-employee-person",
// })
// if (result.success) {
// controlLetter.value = result.body?.data
// }
// })
// #endregion
// #region Functions
@@ -50,19 +50,8 @@ function goBack() {
// #endregion region
// #region Utilities & event handlers
function handleAction(type: string) {
switch (type) {
// case 'edit':
// navigateTo({
// name: 'rehab-encounter-id-surgery-report-control_letter_id-edit',
// params: { id: encounterId, "control_letter_id": controlLetterId },
// })
// break
case 'back':
goBack()
break
}
function handleAction() {
goBack()
}
// #endregion
@@ -72,6 +61,5 @@ function handleAction(type: string) {
<template>
<Header :prep="headerPrep" :ref-search-nav="headerPrep.refSearchNav" />
<AppSurgeryReportDetail :instance="controlLetter" @click="handleAction" />
<AppSurgeryReportDetail :instance="surgeryReport" @click="handleAction" />
</template>
@@ -15,6 +15,7 @@ import { ResponsiblePersonSchema } from '~/schemas/person-relative.schema'
import { uploadAttachment } from '~/services/patient.service'
import { getDetail, update } from '~/services/surgery-report.service'
import type { SurgeryReport } from '~/models/surgery-report'
import ActionDialog from '~/components/pub/my-ui/nav-footer/ba-su.vue'
import { toast } from '~/components/pub/ui/toast'
import { withBase } from '~/models/_base'
@@ -48,24 +49,6 @@ const inputForm = ref<ExposedForm<any> | null>(null)
const surgeryReport = ref({})
const isConfirmationOpen = ref(false)
const selectedOperativeAction = ref<any>(null)
const isOperativeActionDialogOpen = ref(false)
provide("isOperativeActionDialogOpen", isOperativeActionDialogOpen);
const headerPrep: HeaderPrep = {
title: "Pilih Tindakan",
icon: 'i-lucide-history',
}
const refSearchNav: RefSearchNav = {
onClick: () => {
// open filter modal
},
onInput: (val: string) => {
searchInput.value = val
},
onClear: () => {
searchInput.value = ''
},
}
// #endregion
// #region Lifecycle Hooks
@@ -131,13 +114,6 @@ async function handleActionClick(eventType: string) {
function handleCancelAdd() {
isConfirmationOpen.value = false
}
function actionDialogHandler(type: string) {
if (type === 'submit') {
// icdPreview.value.procedures = selectedOperativeAction.value || []
}
isOperativeActionDialogOpen.value = false
}
// #endregion
// #region Watchers
@@ -146,39 +122,17 @@ function actionDialogHandler(type: string) {
<template>
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg font-semibold xl:text-xl">Update Laporan Operasi</div>
<code>{{ selectedOperativeAction }}</code>
<AppSurgeryReportEntry
ref="inputForm"
:schema="SurgeryReportSchema"
:operative-action-list="[]"
/>
<div class="my-2 flex justify-end py-2">
<Action :enable-draft="false" @click="handleActionClick" />
</div>
<Dialog
v-model:open="isOperativeActionDialogOpen"
title=""
size="xl"
>
<Header
:prep="headerPrep"
:ref-search-nav="refSearchNav"
v-model:search="searchInput"
@search="handleSearch" />
<AppSurgeryReportPicker
v-model:model-value="selectedOperativeAction"
:data="data"
:pagination-meta="paginationMeta"
@page-change="handlePageChange"
/>
<div class="my-2 flex justify-end py-2">
<ActionDialog @click="actionDialogHandler" />
</div>
</Dialog>
<Confirmation
v-model:open="isConfirmationOpen"
title="Simpan Data"
+17 -12
View File
@@ -27,6 +27,7 @@ const { data, isLoading, paginationMeta, searchInput, handlePageChange, handleSe
entityName: 'surgery-report',
})
const isHistoryDialogOpen = ref(false)
const isFilterDialogOpen = ref(false)
const isRecordConfirmationOpen = ref(false)
const summaryLoading = ref(false)
@@ -58,6 +59,12 @@ const refSearchNav: RefSearchNav = {
searchInput.value = ''
},
}
headerPrep.components = [
{
component: defineAsyncComponent(() => import('~/components/app/surgery-report/_common/btn-history.vue')),
props: { }
},
];
// #endregion
// #region Lifecycle Hooks
@@ -79,6 +86,9 @@ async function getListData() {
}
// Handle confirmation result
function handleFiltering(value: any) {
isFilterDialogOpen.value = false
}
async function handleConfirmDelete(record: any, action: string) {
if (action === 'delete' && record?.id) {
try {
@@ -108,6 +118,7 @@ provide('rec_id', recId)
provide('rec_action', recAction)
provide('rec_item', recItem)
provide('table_data_loader', isLoading)
provide('isHistoryDialogOpen', isHistoryDialogOpen)
// #endregion
// #region Watchers
@@ -148,10 +159,7 @@ watch([recId, recAction], () => {
]" />
<div v-else>
<Header v-model:search="searchInput"
:prep="headerPrep"
:ref-search-nav="refSearchNav"
@search="handleSearch" />
<Header :prep="headerPrep" />
<Filter
:prep="headerPrep"
@@ -162,14 +170,11 @@ watch([recId, recAction], () => {
<AppSurgeryReportList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
<Dialog v-model:open="isFilterDialogOpen" title="Filter" size="lg">
<!-- <AppEncounterFilter
:installation="{
msg: { placeholder: 'Pilih' },
items: [],
}"
:schema="{}"
/> -->
aaaaaaa
<AppSurgeryReportCommonFilter @submit="handleFiltering" />
</Dialog>
<Dialog v-model:open="isHistoryDialogOpen" title="History">
<AppSurgeryReportCommonHistoryDialog />
</Dialog>
<RecordConfirmation v-model:open="isRecordConfirmationOpen" action="delete" :record="recItem"
@@ -1,13 +1,17 @@
<script setup lang="ts">
defineProps<{
import { cn } from '~/lib/utils';
const props = defineProps<{
label: string
class?: string
labelClass?: string
}>()
</script>
<template>
<div class="flex flex-col gap-1 lg:grid lg:grid-cols-[180px_minmax(0,1fr)] lg:gap-x-3">
<div :class="cn(`flex flex-col gap-1 lg:grid lg:grid-cols-[180px_minmax(0,1fr)] lg:gap-x-3`, props.class)">
<!-- Label -->
<span class="text-md font-normal text-muted-foreground">
<span :class="cn(`text-md font-normal text-muted-foreground`, props.labelClass)">
{{ label }}
</span>
@@ -2,8 +2,6 @@
import { Dialog } from '~/components/pub/ui/dialog'
import DialogContent from '~/components/pub/ui/dialog/DialogContent.vue'
import DialogDescription from '~/components/pub/ui/dialog/DialogDescription.vue'
import DialogContent from '~/components/pub/ui/dialog/DialogContent.vue'
import DialogDescription from '~/components/pub/ui/dialog/DialogDescription.vue'
interface DialogProps {
title: string
@@ -50,8 +50,11 @@ const value = ref({
end: todayCalendar,
}) as Ref<DateRange>
function onInput(event: Event) {
props.refSearchNav?.onInput((event.target as HTMLInputElement).value)
}
function onFilterClick() {
console.log('Search:', searchQuery.value)
console.log('Date Range:', dateRange.value)
props.refSearchNav?.onClick()
}
@@ -60,9 +63,11 @@ function onFilterClick() {
<template>
<header>
<div class="flex items-center gap-2 mb-4 2xl:mb-5">
<div class="relative w-64">
<div v-if="refSearchNav" class="relative w-64">
<Search class="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-gray-400" />
<Input v-model="searchQuery" type="text" placeholder="Cari Nama /No.RM" class="pl-9" />
<Input v-model="searchQuery" @input="onInput"
type="text" placeholder="Cari .." class="pl-9" />
</div>
<Popover>
@@ -7,7 +7,7 @@ definePageMeta({
middleware: ['rbac'],
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
title: 'Detail Surat Kontrol',
contentFrame: 'cf-container-md',
contentFrame: 'cf-full-width',
})
const route = useRoute()