wip: add form data pelaksanaan operasi
todo: blood input section * ui: patch focus ring state * layouts-pages: fix width layout calculation feat(treatment-report): add fill-notes component and validation messages - Add new FillNotes component for tissue notes input with dynamic fields - Update schema validation with required error messages for operation and specimen fields - Adjust form layout to include new FillNotes component and improve field organization cherry-pick arrangement procedure from feat/protokol-terapi-116
This commit is contained in:
+64
@@ -0,0 +1,64 @@
|
|||||||
|
<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 activeKey = ref<string | null>(null)
|
||||||
|
const linkItems: LinkItem[] = [
|
||||||
|
{
|
||||||
|
label: 'Process',
|
||||||
|
onClick: () => {
|
||||||
|
process()
|
||||||
|
},
|
||||||
|
icon: 'i-lucide-arrow-right',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function process() {
|
||||||
|
recId.value = props.rec.id || 0
|
||||||
|
recAction.value = ActionEvents.showProcess
|
||||||
|
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>
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
|
||||||
|
const action = defineAsyncComponent(() => import('./dropdown-action-p.vue'))
|
||||||
|
|
||||||
|
export const config: Config = {
|
||||||
|
cols: [{}, {}, {}, { width: 50 }],
|
||||||
|
|
||||||
|
headers: [[{ label: 'Kode' }, { label: 'Nama (FHIR)' }, { label: 'Nama (ID)' }, { label: '' }]],
|
||||||
|
|
||||||
|
keys: ['code', 'name', 'indName', 'action'],
|
||||||
|
|
||||||
|
delKeyNames: [
|
||||||
|
{ key: 'code', label: 'Kode' },
|
||||||
|
{ key: 'name', label: 'Nama (FHIR)' },
|
||||||
|
{ key: 'indName', label: 'Nama (ID)' },
|
||||||
|
],
|
||||||
|
|
||||||
|
parses: {},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
action(rec, idx) {
|
||||||
|
const res: RecComponent = {
|
||||||
|
idx,
|
||||||
|
rec: rec as object,
|
||||||
|
component: action,
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
htmls: {},
|
||||||
|
}
|
||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
<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'
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
|
import { toast } from '~/components/pub/ui/toast'
|
||||||
|
import { config } from './procedure-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 {
|
||||||
|
processFn: (input: unknown) => void
|
||||||
|
}
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
paginationMeta,
|
||||||
|
searchInput,
|
||||||
|
handlePageChange,
|
||||||
|
handleSearch,
|
||||||
|
fetchData: getItemList,
|
||||||
|
} = usePaginatedList({
|
||||||
|
fetchFn: async (params: any) => {
|
||||||
|
const result = await getList({
|
||||||
|
search: params.search,
|
||||||
|
sort: 'createdAt:desc',
|
||||||
|
'page-number': params['page-number'] || 0,
|
||||||
|
'page-size': params['page-size'] || 10,
|
||||||
|
})
|
||||||
|
return { success: result.success || false, body: result.body || {} }
|
||||||
|
},
|
||||||
|
entityName: 'procedure-src',
|
||||||
|
})
|
||||||
|
|
||||||
|
const headerPrep: HeaderPrep = {
|
||||||
|
title: 'MCU Prosedur',
|
||||||
|
icon: 'i-lucide-clipboard-list',
|
||||||
|
refSearchNav: {
|
||||||
|
placeholder: 'Cari (min. 3 karakter)...',
|
||||||
|
minLength: 3,
|
||||||
|
debounceMs: 500,
|
||||||
|
showValidationFeedback: true,
|
||||||
|
onInput: (val: string) => {
|
||||||
|
searchInput.value = val
|
||||||
|
},
|
||||||
|
onClick: () => {},
|
||||||
|
onClear: () => {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const recId = ref<string>(``)
|
||||||
|
const recAction = ref<string>(``)
|
||||||
|
const recItem = ref<any>({})
|
||||||
|
provide('ap_rec_id', recId)
|
||||||
|
provide('ap_rec_action', recAction)
|
||||||
|
provide('ap_rec_item', recItem)
|
||||||
|
provide('table_data_loader', isLoading)
|
||||||
|
|
||||||
|
const isModalOpen = inject(`isProcedurePickerDialogOpen`) as Ref<boolean>
|
||||||
|
|
||||||
|
watch([recId, recAction], () => {
|
||||||
|
switch (recAction.value) {
|
||||||
|
case ActionEvents.showProcess:
|
||||||
|
props.processFn({
|
||||||
|
id: recId.value,
|
||||||
|
code: recItem.value.code,
|
||||||
|
name: recItem.value.name,
|
||||||
|
})
|
||||||
|
isModalOpen.value = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getItemList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Dialog v-model:open="isModalOpen" title="" size="xl">
|
||||||
|
<Header
|
||||||
|
v-model="searchInput"
|
||||||
|
:prep="headerPrep"
|
||||||
|
:ref-search-nav="headerPrep.refSearchNav"
|
||||||
|
@search="handleSearch"
|
||||||
|
class=""
|
||||||
|
/>
|
||||||
|
<AppProcedureSrcList
|
||||||
|
:table-config="config"
|
||||||
|
:data="data"
|
||||||
|
:pagination-meta="paginationMeta"
|
||||||
|
@page-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import ProcedureListDialog from './procedure-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'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fieldName: string
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const isProcedurePickerDialogOpen = ref<boolean>(false)
|
||||||
|
provide(`isProcedurePickerDialogOpen`, isProcedurePickerDialogOpen)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="">
|
||||||
|
<div class="mb-2 flex items-center justify-between">
|
||||||
|
<h1 class="mb-2 font-medium">{{ title }}</h1>
|
||||||
|
<Button @click="isProcedurePickerDialogOpen = 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 Diagnosis
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FieldArray v-slot="{ fields, push, remove }" :name="props.fieldName">
|
||||||
|
<ProcedureListDialog :process-fn="push" />
|
||||||
|
|
||||||
|
<div class="border border-gray-200 rounded-lg overflow-hidden">
|
||||||
|
<Table>
|
||||||
|
<TableHeader class="bg-gray-100">
|
||||||
|
<TableRow>
|
||||||
|
<TableHead class="w-1/2">Prosedur</TableHead>
|
||||||
|
<TableHead class="w-1/2">ICD-X</TableHead>
|
||||||
|
<TableHead class="w-1/2">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>
|
||||||
@@ -14,7 +14,18 @@ import * as DE from '~/components/pub/my-ui/doc-entry'
|
|||||||
import Separator from '~/components/pub/ui/separator/Separator.vue'
|
import Separator from '~/components/pub/ui/separator/Separator.vue'
|
||||||
|
|
||||||
// form field components
|
// form field components
|
||||||
import { ButtonAction, Fragment, InputBase } from '~/components/pub/my-ui/form/'
|
import {
|
||||||
|
FillNotes,
|
||||||
|
SelectBilling,
|
||||||
|
SelectBirthPlace,
|
||||||
|
SelectBirthType,
|
||||||
|
SelectOperationSystem,
|
||||||
|
SelectOperationType,
|
||||||
|
SelectSpecimen,
|
||||||
|
SelectSurgeryCounter,
|
||||||
|
SelectSurgeryType,
|
||||||
|
} from './fields'
|
||||||
|
import { ButtonAction, Fragment, InputBase, TextAreaInput } from '~/components/pub/my-ui/form/'
|
||||||
import { SelectDoctor } from '~/components/app/doctor/fields'
|
import { SelectDoctor } from '~/components/app/doctor/fields'
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
@@ -38,6 +49,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'error', errors: Error): void
|
(e: 'error', errors: Error): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const tissueNotesLimit = 5
|
||||||
const { isLoading, mode = 'create' } = props
|
const { isLoading, mode = 'create' } = props
|
||||||
const isReadonly = computed(() => {
|
const isReadonly = computed(() => {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -113,39 +125,45 @@ const onSubmit = handleSubmit(
|
|||||||
field-name="operatorTeam.operatorId"
|
field-name="operatorTeam.operatorId"
|
||||||
label="Operator"
|
label="Operator"
|
||||||
placeholder="Masukkan operator"
|
placeholder="Masukkan operator"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
/>
|
/>
|
||||||
<InputBase
|
<InputBase
|
||||||
field-name="assistantOperatorId"
|
field-name="assistantOperatorId"
|
||||||
label="Asisten Operator"
|
label="Asisten Operator"
|
||||||
placeholder="Masukkan asisten operator"
|
placeholder="Masukkan asisten operator"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
/>
|
/>
|
||||||
<InputBase
|
<InputBase
|
||||||
field-name="instrumentNurseId"
|
field-name="instrumentNurseId"
|
||||||
label="Instrumentir"
|
label="Instrumentir"
|
||||||
placeholder="Masukkan instrumentir"
|
placeholder="Masukkan instrumentir"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
/>
|
/>
|
||||||
<InputBase
|
<InputBase
|
||||||
field-name="surgeryDate"
|
field-name="surgeryDate"
|
||||||
label="Tanggal Pembedahan"
|
label="Tanggal Pembedahan"
|
||||||
placeholder="Pilih Tanggal"
|
placeholder="Pilih Tanggal"
|
||||||
icon-name="i-lucide-calendar"
|
icon-name="i-lucide-calendar"
|
||||||
is-disabled
|
:is-disabled="isReadonly"
|
||||||
/>
|
/>
|
||||||
</DE.Block>
|
</DE.Block>
|
||||||
<DE.Block
|
<DE.Block
|
||||||
:col-count="4"
|
:col-count="4"
|
||||||
:cell-flex="false"
|
:cell-flex="false"
|
||||||
>
|
>
|
||||||
<InputBase
|
<TextAreaInput
|
||||||
field-name="actionDiagnosis"
|
field-name="actionDiagnosis"
|
||||||
label="Diagnosa Tindakan"
|
label="Diagnosa Tindakan"
|
||||||
placeholder="Masukkan diagnosa tindakan"
|
placeholder="Masukkan diagnosa tindakan"
|
||||||
:col-span="2"
|
:col-span="2"
|
||||||
|
:rows="5"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
/>
|
/>
|
||||||
<InputBase
|
<InputBase
|
||||||
field-name="postSurgeryNurseId"
|
field-name="postSurgeryNurseId"
|
||||||
label="Perawat Pasca Bedah"
|
label="Perawat Pasca Bedah"
|
||||||
placeholder="Masukkan perawat pasca bedah"
|
placeholder="Masukkan perawat pasca bedah"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
/>
|
/>
|
||||||
</DE.Block>
|
</DE.Block>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
@@ -183,13 +201,194 @@ const onSubmit = handleSubmit(
|
|||||||
:col-count="3"
|
:col-count="3"
|
||||||
:cell-flex="false"
|
:cell-flex="false"
|
||||||
>
|
>
|
||||||
<InputBase
|
<SelectSurgeryType
|
||||||
field-name="operationType"
|
field-name="operationExecution.surgeryType"
|
||||||
label="Tindakan Opearatif/Non"
|
label="Jenis Operasi"
|
||||||
placeholder="Masukkan tindakan operatif/non"
|
placeholder="Pilih jenis operasi"
|
||||||
:col-span="2"
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<SelectBilling
|
||||||
|
field-name="operationExecution.billingCode"
|
||||||
|
label="Kode Billing"
|
||||||
|
placeholder="Pilih kode billing"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<SelectOperationSystem
|
||||||
|
field-name="operationExecution.operationSystem"
|
||||||
|
label="Sistem Operasi"
|
||||||
|
placeholder="Pilih sistem operasi"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
/>
|
/>
|
||||||
</DE.Block>
|
</DE.Block>
|
||||||
|
|
||||||
|
<DE.Block
|
||||||
|
:col-count="3"
|
||||||
|
:cell-flex="false"
|
||||||
|
>
|
||||||
|
<InputBase
|
||||||
|
field-name="operationExecution.operationStartAt"
|
||||||
|
label="Operasi Mulai"
|
||||||
|
placeholder="Pilih Tanggal"
|
||||||
|
icon-name="i-lucide-calendar"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<InputBase
|
||||||
|
field-name="operationExecution.operationEndAt"
|
||||||
|
label="Operasi Selesai"
|
||||||
|
placeholder="Pilih Tanggal"
|
||||||
|
icon-name="i-lucide-calendar"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<InputBase
|
||||||
|
field-name="_operationDuration"
|
||||||
|
label="Lama Operasi"
|
||||||
|
placeholder="03 Jam 20 Menit"
|
||||||
|
is-disabled
|
||||||
|
/>
|
||||||
|
</DE.Block>
|
||||||
|
|
||||||
|
<DE.Block
|
||||||
|
:col-count="3"
|
||||||
|
:cell-flex="false"
|
||||||
|
>
|
||||||
|
<InputBase
|
||||||
|
field-name="operationExecution.anesthesiaStartAt"
|
||||||
|
label="Pembiusan Mulai"
|
||||||
|
placeholder="Pilih Tanggal"
|
||||||
|
icon-name="i-lucide-calendar"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<InputBase
|
||||||
|
field-name="operationExecution.anesthesiaEndAt"
|
||||||
|
label="Pembiusan Selesai"
|
||||||
|
placeholder="Pilih Tanggal"
|
||||||
|
icon-name="i-lucide-calendar"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<InputBase
|
||||||
|
field-name="_anesthesiaDuration"
|
||||||
|
label="Lama Pembiusan"
|
||||||
|
placeholder="03 Jam 20 Menit"
|
||||||
|
is-disabled
|
||||||
|
/>
|
||||||
|
</DE.Block>
|
||||||
|
|
||||||
|
<DE.Block
|
||||||
|
:col-count="3"
|
||||||
|
:cell-flex="false"
|
||||||
|
>
|
||||||
|
<SelectOperationType
|
||||||
|
field-name="operationExecution.surgeryCleanType"
|
||||||
|
label="Jenis Pembedahan"
|
||||||
|
placeholder="Pilih jenis pembedahan"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<SelectSurgeryCounter
|
||||||
|
field-name="operationExecution.surgeryNumber"
|
||||||
|
label="Operasi Ke"
|
||||||
|
placeholder="Pilih"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<SelectBirthType
|
||||||
|
field-name="operationExecution.birthRemark"
|
||||||
|
label="Keterangan Lahir"
|
||||||
|
placeholder="Pilih"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<SelectBirthPlace
|
||||||
|
field-name="operationExecution.birthPlaceNote"
|
||||||
|
label="Ket. Tempat Lahir"
|
||||||
|
placeholder="Pilih keterangan tempat lahir"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputBase
|
||||||
|
field-name="operationExecution.personWeight"
|
||||||
|
label="Berat Badan"
|
||||||
|
placeholder="Masukkan berat badan"
|
||||||
|
numeric-only
|
||||||
|
suffix-msg="Gram"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<InputBase
|
||||||
|
field-name="operationExecution.birthCondition"
|
||||||
|
label="Ket. Saat Lahir"
|
||||||
|
placeholder="Tambah catatan"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
</DE.Block>
|
||||||
|
|
||||||
|
<DE.Block
|
||||||
|
:col-count="4"
|
||||||
|
:cell-flex="false"
|
||||||
|
>
|
||||||
|
<TextAreaInput
|
||||||
|
field-name="operationExecution.operationDescription"
|
||||||
|
label="Uraian Operasi"
|
||||||
|
placeholder="Masukkan uraian"
|
||||||
|
:rows="3"
|
||||||
|
:col-span="2"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<InputBase
|
||||||
|
field-name="operationExecution.bleedingAmountCc"
|
||||||
|
label="Jumlah Pendarahan"
|
||||||
|
placeholder="Masukkan jumlah pendarahan"
|
||||||
|
suffix-msg="CC"
|
||||||
|
numeric-only
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
</DE.Block>
|
||||||
|
<DE.Block
|
||||||
|
:col-count="3"
|
||||||
|
:cell-flex="false"
|
||||||
|
>
|
||||||
|
bloods
|
||||||
|
</DE.Block>
|
||||||
|
|
||||||
|
<DE.Block
|
||||||
|
:col-count="3"
|
||||||
|
:cell-flex="false"
|
||||||
|
>
|
||||||
|
<InputBase
|
||||||
|
field-name="implant.brand"
|
||||||
|
label="Merk"
|
||||||
|
placeholder="Masukkan merk"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputBase
|
||||||
|
field-name="implant.name"
|
||||||
|
label="Nama Implant"
|
||||||
|
placeholder="Masukkan nama implant"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputBase
|
||||||
|
field-name="implant.sticker"
|
||||||
|
label="Sticker/Nomer Register Implant"
|
||||||
|
placeholder="Masukkan sticker/nomor register implant"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputBase
|
||||||
|
field-name="implant.companionName"
|
||||||
|
label="Nama Pendamping Implant"
|
||||||
|
placeholder="Masukkan nama pendamping implant"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<SelectSpecimen
|
||||||
|
field-name="specimen.destination"
|
||||||
|
label="Specimen/Jaringan dikirim ke"
|
||||||
|
placeholder="Pilih"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
</DE.Block>
|
||||||
|
<FillNotes
|
||||||
|
title="Keterangan Jaringan"
|
||||||
|
:limit="tissueNotesLimit"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
<div class="mt-4 flex justify-end">
|
<div class="mt-4 flex justify-end">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// components
|
||||||
|
import { FieldArray } from 'vee-validate'
|
||||||
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
|
||||||
|
// form field components
|
||||||
|
import { ButtonAction, InputBase } from '~/components/pub/my-ui/form'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isDisabled: boolean
|
||||||
|
limit: number
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const isReadonly = computed(() => props.isDisabled)
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<FieldArray
|
||||||
|
v-slot="{ fields, push, remove }"
|
||||||
|
name="tissueNotes"
|
||||||
|
>
|
||||||
|
<template v-if="fields.length === 0">
|
||||||
|
{{ push({ note: '' }) }}
|
||||||
|
</template>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<DE.Block
|
||||||
|
v-for="(field, idx) in fields"
|
||||||
|
:key="field.key"
|
||||||
|
:col-count="3"
|
||||||
|
:cell-flex="false"
|
||||||
|
>
|
||||||
|
<InputBase
|
||||||
|
:label="idx === 0 ? 'Keterangan Jaringan' : undefined"
|
||||||
|
:field-name="`tissueNotes[${idx}].note`"
|
||||||
|
placeholder="Masukkan catatan"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<DE.Cell class="flex items-start justify-start">
|
||||||
|
<DE.Field :class="idx === 0 ? 'mt-[30px]' : 'mt-0'">
|
||||||
|
<ButtonAction
|
||||||
|
v-if="idx !== 0"
|
||||||
|
:disabled="isReadonly"
|
||||||
|
preset="delete"
|
||||||
|
:title="`Hapus Kontak ${idx + 1}`"
|
||||||
|
icon-only
|
||||||
|
@click="remove(idx)"
|
||||||
|
/>
|
||||||
|
</DE.Field>
|
||||||
|
</DE.Cell>
|
||||||
|
</DE.Block>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ButtonAction
|
||||||
|
preset="add"
|
||||||
|
label="Tambah Catatan"
|
||||||
|
title="Tambah Catatan Keterangan Jaringan"
|
||||||
|
:disabled="fields.length >= limit || isReadonly"
|
||||||
|
:full-width-mobile="true"
|
||||||
|
class="mt-4"
|
||||||
|
@click="push({ name: '', dose: '', unit: '' })"
|
||||||
|
/>
|
||||||
|
</FieldArray>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export { default as FillNotes } from './fill-notes.vue'
|
||||||
|
export { default as SelectBilling } from './select-billing.vue'
|
||||||
|
export { default as SelectBirthPlace } from './select-birth-place.vue'
|
||||||
|
export { default as SelectBirthType } from './select-birth-type.vue'
|
||||||
|
export { default as SelectOperationSystem } from './select-operation-system.vue'
|
||||||
|
export { default as SelectOperationType } from './select-operation-type.vue'
|
||||||
|
export { default as SelectSpecimen } from './select-specimen.vue'
|
||||||
|
export { default as SelectSurgeryCounter } from './select-surgery-counter.vue'
|
||||||
|
export { default as SelectSurgeryType } from './select-surgery-type.vue'
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormErrors } from '~/types/error'
|
||||||
|
import Select from '~/components/pub/my-ui/form/select.vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
fieldName: string
|
||||||
|
label: string
|
||||||
|
isDisabled?: boolean
|
||||||
|
isRequired?: boolean
|
||||||
|
placeholder?: string
|
||||||
|
errors?: FormErrors
|
||||||
|
class?: string
|
||||||
|
selectClass?: string
|
||||||
|
fieldGroupClass?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||||
|
|
||||||
|
const opts = [
|
||||||
|
{ label: 'General', value: 'general' },
|
||||||
|
{ label: 'Regional', value: 'regional' },
|
||||||
|
{ label: 'Local', value: 'local' },
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||||
|
<DE.Label
|
||||||
|
label-for="fieldName"
|
||||||
|
: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>
|
||||||
|
<Select
|
||||||
|
:id="fieldName"
|
||||||
|
:is-disabled="isDisabled"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="opts"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:preserve-order="false"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||||
|
selectClass,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</DE.Field>
|
||||||
|
</DE.Cell>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormErrors } from '~/types/error'
|
||||||
|
import Select from '~/components/pub/my-ui/form/select.vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
fieldName: string
|
||||||
|
label: string
|
||||||
|
isDisabled?: boolean
|
||||||
|
isRequired?: boolean
|
||||||
|
placeholder?: string
|
||||||
|
errors?: FormErrors
|
||||||
|
class?: string
|
||||||
|
selectClass?: string
|
||||||
|
fieldGroupClass?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||||
|
|
||||||
|
const opts = [
|
||||||
|
{ label: 'RSSA', value: 'rssa' },
|
||||||
|
{ label: 'Bidan Luar', value: 'out1' },
|
||||||
|
{ label: 'Dokter Luar', value: 'out2' },
|
||||||
|
{ label: 'Dukun Bayi', value: 'out3' },
|
||||||
|
{ label: 'Puskesmas', value: 'out4' },
|
||||||
|
{ label: 'Paramedis Luar', value: 'out5' },
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||||
|
<DE.Label
|
||||||
|
label-for="fieldName"
|
||||||
|
: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>
|
||||||
|
<Select
|
||||||
|
:id="fieldName"
|
||||||
|
:is-disabled="isDisabled"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="opts"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:preserve-order="false"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||||
|
selectClass,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</DE.Field>
|
||||||
|
</DE.Cell>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormErrors } from '~/types/error'
|
||||||
|
import Select from '~/components/pub/my-ui/form/select.vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
fieldName: string
|
||||||
|
label: string
|
||||||
|
isDisabled?: boolean
|
||||||
|
isRequired?: boolean
|
||||||
|
placeholder?: string
|
||||||
|
errors?: FormErrors
|
||||||
|
class?: string
|
||||||
|
selectClass?: string
|
||||||
|
fieldGroupClass?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||||
|
|
||||||
|
const opts = [
|
||||||
|
{ label: 'Lahir Hidup', value: 'lahir_hidup' },
|
||||||
|
{ label: 'Lahir Mati', value: 'lahir_mati' },
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||||
|
<DE.Label
|
||||||
|
label-for="fieldName"
|
||||||
|
: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>
|
||||||
|
<Select
|
||||||
|
:id="fieldName"
|
||||||
|
:is-disabled="isDisabled"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="opts"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:preserve-order="false"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||||
|
selectClass,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</DE.Field>
|
||||||
|
</DE.Cell>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormErrors } from '~/types/error'
|
||||||
|
import Select from '~/components/pub/my-ui/form/select.vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
fieldName: string
|
||||||
|
label: string
|
||||||
|
isDisabled?: boolean
|
||||||
|
isRequired?: boolean
|
||||||
|
placeholder?: string
|
||||||
|
errors?: FormErrors
|
||||||
|
class?: string
|
||||||
|
selectClass?: string
|
||||||
|
fieldGroupClass?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||||
|
|
||||||
|
const opts = [
|
||||||
|
{ label: 'Cito', value: 'cito' },
|
||||||
|
{ label: 'Urgent', value: 'urgent' },
|
||||||
|
{ label: 'Efektif', value: 'efektif' },
|
||||||
|
{ label: 'Khusus', value: 'khusus' },
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||||
|
<DE.Label
|
||||||
|
label-for="fieldName"
|
||||||
|
: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>
|
||||||
|
<Select
|
||||||
|
:id="fieldName"
|
||||||
|
:is-disabled="isDisabled"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="opts"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:preserve-order="false"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||||
|
selectClass,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</DE.Field>
|
||||||
|
</DE.Cell>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormErrors } from '~/types/error'
|
||||||
|
import Select from '~/components/pub/my-ui/form/select.vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
fieldName: string
|
||||||
|
label: string
|
||||||
|
isDisabled?: boolean
|
||||||
|
isRequired?: boolean
|
||||||
|
placeholder?: string
|
||||||
|
errors?: FormErrors
|
||||||
|
class?: string
|
||||||
|
selectClass?: string
|
||||||
|
fieldGroupClass?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||||
|
|
||||||
|
const opts = [
|
||||||
|
{ label: 'Bersih', value: 'bersih' },
|
||||||
|
{ label: 'Bersih Terkontaminasi', value: 'bersih_terkontaminasi' },
|
||||||
|
{ label: 'Terkontaminasi Kotor', value: 'terkontaminasi' },
|
||||||
|
{ label: 'Kotor', value: 'kotor' },
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||||
|
<DE.Label
|
||||||
|
label-for="fieldName"
|
||||||
|
: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>
|
||||||
|
<Select
|
||||||
|
:id="fieldName"
|
||||||
|
:is-disabled="isDisabled"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="opts"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:preserve-order="false"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||||
|
selectClass,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</DE.Field>
|
||||||
|
</DE.Cell>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormErrors } from '~/types/error'
|
||||||
|
import Select from '~/components/pub/my-ui/form/select.vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
fieldName: string
|
||||||
|
label: string
|
||||||
|
isDisabled?: boolean
|
||||||
|
isRequired?: boolean
|
||||||
|
placeholder?: string
|
||||||
|
errors?: FormErrors
|
||||||
|
class?: string
|
||||||
|
selectClass?: string
|
||||||
|
fieldGroupClass?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||||
|
|
||||||
|
const opts = [
|
||||||
|
{ label: 'PA', value: 'pa' },
|
||||||
|
{ label: 'Mikrobiologi', value: 'microbiology' },
|
||||||
|
{ label: 'Laborat', value: 'laboratory' },
|
||||||
|
{ label: 'Tidak Perlu', value: 'none' },
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||||
|
<DE.Label
|
||||||
|
label-for="fieldName"
|
||||||
|
: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>
|
||||||
|
<Select
|
||||||
|
:id="fieldName"
|
||||||
|
:is-disabled="isDisabled"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="opts"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:preserve-order="false"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||||
|
selectClass,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</DE.Field>
|
||||||
|
</DE.Cell>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormErrors } from '~/types/error'
|
||||||
|
import Select from '~/components/pub/my-ui/form/select.vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
fieldName: string
|
||||||
|
label: string
|
||||||
|
isDisabled?: boolean
|
||||||
|
isRequired?: boolean
|
||||||
|
placeholder?: string
|
||||||
|
errors?: FormErrors
|
||||||
|
class?: string
|
||||||
|
selectClass?: string
|
||||||
|
fieldGroupClass?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||||
|
|
||||||
|
const opts = [
|
||||||
|
{ label: '1 (Satu)', value: 'first' },
|
||||||
|
{ label: 'Ulangan', value: 'retry' },
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||||
|
<DE.Label
|
||||||
|
label-for="fieldName"
|
||||||
|
: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>
|
||||||
|
<Select
|
||||||
|
:id="fieldName"
|
||||||
|
:is-disabled="isDisabled"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="opts"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:preserve-order="false"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||||
|
selectClass,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</DE.Field>
|
||||||
|
</DE.Cell>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormErrors } from '~/types/error'
|
||||||
|
import Select from '~/components/pub/my-ui/form/select.vue'
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
fieldName: string
|
||||||
|
label: string
|
||||||
|
isDisabled?: boolean
|
||||||
|
isRequired?: boolean
|
||||||
|
placeholder?: string
|
||||||
|
errors?: FormErrors
|
||||||
|
class?: string
|
||||||
|
selectClass?: string
|
||||||
|
fieldGroupClass?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||||
|
|
||||||
|
const opts = [
|
||||||
|
{ label: 'Kecil', value: 'kecil' },
|
||||||
|
{ label: 'Sedang', value: 'sedang' },
|
||||||
|
{ label: 'Besar', value: 'besar' },
|
||||||
|
{ label: 'Khusus', value: 'khusus' },
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||||
|
<DE.Label
|
||||||
|
label-for="fieldName"
|
||||||
|
: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>
|
||||||
|
<Select
|
||||||
|
:id="fieldName"
|
||||||
|
:is-disabled="isDisabled"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="opts"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:preserve-order="false"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||||
|
selectClass,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</DE.Field>
|
||||||
|
</DE.Cell>
|
||||||
|
</template>
|
||||||
@@ -76,10 +76,11 @@ const hasError = computed(() => !!fieldError.value)
|
|||||||
v-bind="componentField"
|
v-bind="componentField"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:maxlength="maxLength"
|
:maxlength="maxLength"
|
||||||
:class="cn(
|
:class="
|
||||||
'focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0',
|
cn(
|
||||||
hasError && 'border-red-500 focus:border-red-500 focus:ring-red-500'
|
hasError && 'border-red-500 focus-visible:border-red-500 focus-visible:ring-red-500',
|
||||||
)"
|
)
|
||||||
|
"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
aria-autocomplete="none"
|
aria-autocomplete="none"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
|
|||||||
@@ -19,8 +19,11 @@ const props = defineProps<{
|
|||||||
maxLength?: number
|
maxLength?: number
|
||||||
isRequired?: boolean
|
isRequired?: boolean
|
||||||
isDisabled?: boolean
|
isDisabled?: boolean
|
||||||
|
resize?: 'none' | 'y' | 'x'
|
||||||
|
rows?: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const { resize = 'none' } = props
|
||||||
function handleInput(event: Event) {
|
function handleInput(event: Event) {
|
||||||
const target = event.target as HTMLInputElement
|
const target = event.target as HTMLInputElement
|
||||||
let value = target.value
|
let value = target.value
|
||||||
@@ -47,7 +50,7 @@ function handleInput(event: Event) {
|
|||||||
<template>
|
<template>
|
||||||
<DE.Cell :col-span="colSpan || 1">
|
<DE.Cell :col-span="colSpan || 1">
|
||||||
<DE.Label
|
<DE.Label
|
||||||
class="mb-1"
|
:class="class"
|
||||||
v-if="label !== ''"
|
v-if="label !== ''"
|
||||||
:label-for="fieldName"
|
:label-for="fieldName"
|
||||||
:is-required="isRequired && !isDisabled"
|
:is-required="isRequired && !isDisabled"
|
||||||
@@ -65,11 +68,19 @@ function handleInput(event: Event) {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea
|
<Textarea
|
||||||
|
:rows="rows"
|
||||||
:disabled="isDisabled"
|
:disabled="isDisabled"
|
||||||
v-bind="componentField"
|
v-bind="componentField"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:maxlength="maxLength"
|
:maxlength="maxLength"
|
||||||
:class="cn('focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0')"
|
:class="
|
||||||
|
cn(
|
||||||
|
'focus:border-primary focus:ring-primary focus:ring-offset-0',
|
||||||
|
resize === 'y' && 'resize-y',
|
||||||
|
resize === 'x' && 'resize-x',
|
||||||
|
resize === 'none' && 'resize-none',
|
||||||
|
)
|
||||||
|
"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
aria-autocomplete="none"
|
aria-autocomplete="none"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
@@ -83,4 +94,4 @@ function handleInput(event: Event) {
|
|||||||
</FormField>
|
</FormField>
|
||||||
</DE.Field>
|
</DE.Field>
|
||||||
</DE.Cell>
|
</DE.Cell>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const modelValue = useVModel(props, 'modelValue', emits, {
|
|||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'border-input dark:bg-slate-950 ring-offset-background placeholder:text-muted-foreground flex h-9 md:h-8 2xl:h-9 w-full rounded-md border border-gray-400 px-3 py-2 md:text-xs 2xl:text-sm file:border-0 file:bg-transparent md:file:!text-xs xl:file:!text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50',
|
'border-input dark:bg-slate-950 ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary focus-visible:ring-offset-0 focus-visible:border-primary flex h-9 md:h-8 2xl:h-9 w-full rounded-md border border-gray-400 px-3 py-2 md:text-xs 2xl:text-sm file:border-0 file:bg-transparent md:file:!text-xs xl:file:!text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
props.class,
|
props.class,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
+48
-40
@@ -2,76 +2,84 @@
|
|||||||
import CardContent from '~/components/pub/ui/card/CardContent.vue'
|
import CardContent from '~/components/pub/ui/card/CardContent.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const contentFrame = computed(() => route.meta.contentFrame)
|
|
||||||
|
// Ambil meta dan set default
|
||||||
|
const contentFrame = computed(() => route.meta.contentFrame ?? 'cf-container-lg')
|
||||||
|
|
||||||
|
// Mapping aman supaya tidak ada class invalid
|
||||||
const contentFrameClass = computed(() => {
|
const contentFrameClass = computed(() => {
|
||||||
switch (contentFrame.value) {
|
const allowed = [
|
||||||
case 'cf-container-2xl':
|
'cf-container-2xl',
|
||||||
return 'cf-container-2xl'
|
'cf-container-xl',
|
||||||
case 'cf-container-xl':
|
'cf-container-lg',
|
||||||
return 'cf-container-xl'
|
'cf-container-md',
|
||||||
case 'cf-container-lg':
|
'cf-container-sm',
|
||||||
return 'cf-container-lg'
|
'cf-full-width',
|
||||||
case 'cf-container-md':
|
'cf-no-frame',
|
||||||
return 'cf-container-md'
|
]
|
||||||
case 'cf-container-sm':
|
return allowed.includes(contentFrame.value as string) ? contentFrame.value : 'cf-container-lg'
|
||||||
return 'cf-container-sm'
|
|
||||||
case 'cf-full-width':
|
|
||||||
return 'cf-full-width'
|
|
||||||
default:
|
|
||||||
return 'cf-container-lg'
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<LayoutAppSidebar />
|
<LayoutAppSidebar />
|
||||||
|
|
||||||
<SidebarInset>
|
<SidebarInset>
|
||||||
<LayoutHeader />
|
<LayoutHeader />
|
||||||
<div :class="`w-full p-4 2xl:p-5 flex justify-center ${contentFrameClass}`">
|
|
||||||
<div v-if="contentFrame !== 'cf-no-frame'">
|
<!-- Outer wrapper always full width -->
|
||||||
<Card>
|
<div class="flex w-full justify-center p-4 2xl:p-5">
|
||||||
<CardContent>
|
<!-- Frame mode (container + card) -->
|
||||||
<slot />
|
<template v-if="contentFrameClass !== 'cf-no-frame'">
|
||||||
</CardContent>
|
<div :class="['content-frame', contentFrameClass]">
|
||||||
</Card>
|
<Card>
|
||||||
</div>
|
<CardContent>
|
||||||
<slot v-else />
|
<slot />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- No frame → direct slot -->
|
||||||
|
<template v-else>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</SidebarInset>
|
</SidebarInset>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.cf-container > *,
|
/* Base: semua frame full width by default */
|
||||||
.cf-container-lg > *,
|
.content-frame {
|
||||||
.cf-container-md > *,
|
|
||||||
.cf-container-sm > *,
|
|
||||||
.cf-full-width {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cf-full-width > * {
|
/* Max-width container variants */
|
||||||
width: 100%;
|
.cf-container-2xl {
|
||||||
}
|
|
||||||
|
|
||||||
.cf-container-2xl > * {
|
|
||||||
max-width: 1400px;
|
max-width: 1400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cf-container-xl > * {
|
.cf-container-xl {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cf-container-lg > * {
|
.cf-container-lg {
|
||||||
max-width: 992px;
|
max-width: 992px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cf-container-md > * {
|
.cf-container-md {
|
||||||
max-width: 768px;
|
max-width: 768px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cf-container-sm > * {
|
.cf-container-sm {
|
||||||
max-width: 576px;
|
max-width: 576px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Full width (no max width) */
|
||||||
|
.cf-full-width {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ definePageMeta({
|
|||||||
middleware: ['rbac'],
|
middleware: ['rbac'],
|
||||||
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
||||||
title: 'Tambah Laporan',
|
title: 'Tambah Laporan',
|
||||||
contentFrame: 'cf-full-width',
|
contentFrame: 'cf-container-2xl',
|
||||||
})
|
})
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
const isoDateTime = z.string().min(1, 'Tanggal / waktu wajib diisi')
|
const isoDateTime = z.string().min(1, 'Tanggal / waktu wajib diisi')
|
||||||
const positiveInt = z.number().int().nonnegative()
|
const positiveInt = z.coerce.number().int().nonnegative()
|
||||||
|
|
||||||
const OperatorTeamSchema = z.object({
|
const OperatorTeamSchema = z.object({
|
||||||
dpjpId: z.coerce
|
dpjpId: z.coerce
|
||||||
@@ -30,9 +30,11 @@ const ProcedureSchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const OperationExecutionSchema = z.object({
|
const OperationExecutionSchema = z.object({
|
||||||
surgeryType: z.enum(['kecil', 'sedang', 'besar', 'khusus']),
|
surgeryType: z.enum(['kecil', 'sedang', 'besar', 'khusus'], { required_error: 'Silahkan pilih jenis operasi' }),
|
||||||
billingCode: z.string().min(1),
|
billingCode: z.string({
|
||||||
operationSystem: z.enum(['khusus', 'cito', 'elektif']),
|
required_error: 'Silahkan pilih kode billing',
|
||||||
|
}),
|
||||||
|
operationSystem: z.enum(['khusus', 'cito', 'efektif', 'urgent'], { required_error: 'Silahkan pilih sistem operasi' }),
|
||||||
|
|
||||||
operationStartAt: isoDateTime,
|
operationStartAt: isoDateTime,
|
||||||
operationEndAt: isoDateTime,
|
operationEndAt: isoDateTime,
|
||||||
@@ -41,17 +43,19 @@ const OperationExecutionSchema = z.object({
|
|||||||
anesthesiaEndAt: isoDateTime,
|
anesthesiaEndAt: isoDateTime,
|
||||||
|
|
||||||
surgeryCleanType: z.enum(['bersih', 'bersih_terkontaminasi', 'terkontaminasi', 'kotor']).optional(),
|
surgeryCleanType: z.enum(['bersih', 'bersih_terkontaminasi', 'terkontaminasi', 'kotor']).optional(),
|
||||||
surgeryNumber: z.enum(['1', '2', '3', '4+', 'tidak_diketahui']).optional(),
|
surgeryNumber: z.enum(['first', 'retry']).optional(),
|
||||||
|
|
||||||
birthPlaceNote: z.string().optional(),
|
birthPlaceNote: z.string().optional(),
|
||||||
babyWeightGram: positiveInt.optional(),
|
personWeight: positiveInt.optional(),
|
||||||
birthCondition: z.string().optional(),
|
birthCondition: z.string().optional(),
|
||||||
|
|
||||||
operationDescription: z.string().min(1),
|
operationDescription: z.string({
|
||||||
|
required_error: 'Mohon lengkapi uraian operasi',
|
||||||
|
}),
|
||||||
|
|
||||||
bleedingAmountCc: positiveInt.optional(),
|
bleedingAmountCc: positiveInt.optional(),
|
||||||
|
|
||||||
birthRemark: z.enum(['lahir_hidup', 'lahir_mati', 'abortus', 'tidak_diketahui']).optional(),
|
birthRemark: z.enum(['lahir_hidup', 'lahir_mati']).optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const BloodComponentSchema = z.object({
|
const BloodComponentSchema = z.object({
|
||||||
@@ -74,11 +78,17 @@ const ImplantSchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const SpecimenSchema = z.object({
|
const SpecimenSchema = z.object({
|
||||||
destination: z.string().min(1),
|
destination: z.string({
|
||||||
|
required_error: 'Silahkan pilih specimen',
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const TissueNoteSchema = z.object({
|
const TissueNoteSchema = z.object({
|
||||||
note: z.string().min(1),
|
note: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Masukkan deskripsi catatan',
|
||||||
|
})
|
||||||
|
.min(1, { message: 'Setidaknya diperlukan 1 catatan' }),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const TreatmentReportSchema = z.object({
|
export const TreatmentReportSchema = z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user