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'
|
||||
|
||||
// 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'
|
||||
// Helpers
|
||||
|
||||
@@ -38,6 +49,7 @@ const emit = defineEmits<{
|
||||
(e: 'error', errors: Error): void
|
||||
}>()
|
||||
|
||||
const tissueNotesLimit = 5
|
||||
const { isLoading, mode = 'create' } = props
|
||||
const isReadonly = computed(() => {
|
||||
if (isLoading) {
|
||||
@@ -113,39 +125,45 @@ const onSubmit = handleSubmit(
|
||||
field-name="operatorTeam.operatorId"
|
||||
label="Operator"
|
||||
placeholder="Masukkan operator"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="assistantOperatorId"
|
||||
label="Asisten Operator"
|
||||
placeholder="Masukkan asisten operator"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="instrumentNurseId"
|
||||
label="Instrumentir"
|
||||
placeholder="Masukkan instrumentir"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="surgeryDate"
|
||||
label="Tanggal Pembedahan"
|
||||
placeholder="Pilih Tanggal"
|
||||
icon-name="i-lucide-calendar"
|
||||
is-disabled
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
</DE.Block>
|
||||
<DE.Block
|
||||
:col-count="4"
|
||||
:cell-flex="false"
|
||||
>
|
||||
<InputBase
|
||||
<TextAreaInput
|
||||
field-name="actionDiagnosis"
|
||||
label="Diagnosa Tindakan"
|
||||
placeholder="Masukkan diagnosa tindakan"
|
||||
:col-span="2"
|
||||
:rows="5"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="postSurgeryNurseId"
|
||||
label="Perawat Pasca Bedah"
|
||||
placeholder="Masukkan perawat pasca bedah"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
</DE.Block>
|
||||
</Fragment>
|
||||
@@ -183,13 +201,194 @@ const onSubmit = handleSubmit(
|
||||
:col-count="3"
|
||||
:cell-flex="false"
|
||||
>
|
||||
<InputBase
|
||||
field-name="operationType"
|
||||
label="Tindakan Opearatif/Non"
|
||||
placeholder="Masukkan tindakan operatif/non"
|
||||
:col-span="2"
|
||||
<SelectSurgeryType
|
||||
field-name="operationExecution.surgeryType"
|
||||
label="Jenis Operasi"
|
||||
placeholder="Pilih jenis operasi"
|
||||
: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
|
||||
: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>
|
||||
<div class="mt-4 flex justify-end">
|
||||
<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"
|
||||
:placeholder="placeholder"
|
||||
:maxlength="maxLength"
|
||||
:class="cn(
|
||||
'focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0',
|
||||
hasError && 'border-red-500 focus:border-red-500 focus:ring-red-500'
|
||||
)"
|
||||
:class="
|
||||
cn(
|
||||
hasError && 'border-red-500 focus-visible:border-red-500 focus-visible:ring-red-500',
|
||||
)
|
||||
"
|
||||
autocomplete="off"
|
||||
aria-autocomplete="none"
|
||||
autocorrect="off"
|
||||
|
||||
@@ -19,8 +19,11 @@ const props = defineProps<{
|
||||
maxLength?: number
|
||||
isRequired?: boolean
|
||||
isDisabled?: boolean
|
||||
resize?: 'none' | 'y' | 'x'
|
||||
rows?: number
|
||||
}>()
|
||||
|
||||
const { resize = 'none' } = props
|
||||
function handleInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement
|
||||
let value = target.value
|
||||
@@ -47,7 +50,7 @@ function handleInput(event: Event) {
|
||||
<template>
|
||||
<DE.Cell :col-span="colSpan || 1">
|
||||
<DE.Label
|
||||
class="mb-1"
|
||||
:class="class"
|
||||
v-if="label !== ''"
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired && !isDisabled"
|
||||
@@ -65,11 +68,19 @@ function handleInput(event: Event) {
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
:rows="rows"
|
||||
:disabled="isDisabled"
|
||||
v-bind="componentField"
|
||||
:placeholder="placeholder"
|
||||
: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"
|
||||
aria-autocomplete="none"
|
||||
autocorrect="off"
|
||||
@@ -83,4 +94,4 @@ function handleInput(event: Event) {
|
||||
</FormField>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -24,7 +24,7 @@ const modelValue = useVModel(props, 'modelValue', emits, {
|
||||
v-model="modelValue"
|
||||
:class="
|
||||
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,
|
||||
)
|
||||
"
|
||||
|
||||
+48
-40
@@ -2,76 +2,84 @@
|
||||
import CardContent from '~/components/pub/ui/card/CardContent.vue'
|
||||
|
||||
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(() => {
|
||||
switch (contentFrame.value) {
|
||||
case 'cf-container-2xl':
|
||||
return 'cf-container-2xl'
|
||||
case 'cf-container-xl':
|
||||
return 'cf-container-xl'
|
||||
case 'cf-container-lg':
|
||||
return 'cf-container-lg'
|
||||
case 'cf-container-md':
|
||||
return 'cf-container-md'
|
||||
case 'cf-container-sm':
|
||||
return 'cf-container-sm'
|
||||
case 'cf-full-width':
|
||||
return 'cf-full-width'
|
||||
default:
|
||||
return 'cf-container-lg'
|
||||
}
|
||||
const allowed = [
|
||||
'cf-container-2xl',
|
||||
'cf-container-xl',
|
||||
'cf-container-lg',
|
||||
'cf-container-md',
|
||||
'cf-container-sm',
|
||||
'cf-full-width',
|
||||
'cf-no-frame',
|
||||
]
|
||||
return allowed.includes(contentFrame.value as string) ? contentFrame.value : 'cf-container-lg'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SidebarProvider>
|
||||
<LayoutAppSidebar />
|
||||
|
||||
<SidebarInset>
|
||||
<LayoutHeader />
|
||||
<div :class="`w-full p-4 2xl:p-5 flex justify-center ${contentFrameClass}`">
|
||||
<div v-if="contentFrame !== 'cf-no-frame'">
|
||||
<Card>
|
||||
<CardContent>
|
||||
<slot />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<slot v-else />
|
||||
|
||||
<!-- Outer wrapper always full width -->
|
||||
<div class="flex w-full justify-center p-4 2xl:p-5">
|
||||
<!-- Frame mode (container + card) -->
|
||||
<template v-if="contentFrameClass !== 'cf-no-frame'">
|
||||
<div :class="['content-frame', contentFrameClass]">
|
||||
<Card>
|
||||
<CardContent>
|
||||
<slot />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- No frame → direct slot -->
|
||||
<template v-else>
|
||||
<slot />
|
||||
</template>
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.cf-container > *,
|
||||
.cf-container-lg > *,
|
||||
.cf-container-md > *,
|
||||
.cf-container-sm > *,
|
||||
.cf-full-width {
|
||||
/* Base: semua frame full width by default */
|
||||
.content-frame {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cf-full-width > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cf-container-2xl > * {
|
||||
/* Max-width container variants */
|
||||
.cf-container-2xl {
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
.cf-container-xl > * {
|
||||
.cf-container-xl {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.cf-container-lg > * {
|
||||
.cf-container-lg {
|
||||
max-width: 992px;
|
||||
}
|
||||
|
||||
.cf-container-md > * {
|
||||
.cf-container-md {
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
.cf-container-sm > * {
|
||||
.cf-container-sm {
|
||||
max-width: 576px;
|
||||
}
|
||||
|
||||
/* Full width (no max width) */
|
||||
.cf-full-width {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,7 @@ definePageMeta({
|
||||
middleware: ['rbac'],
|
||||
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
||||
title: 'Tambah Laporan',
|
||||
contentFrame: 'cf-full-width',
|
||||
contentFrame: 'cf-container-2xl',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
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({
|
||||
dpjpId: z.coerce
|
||||
@@ -30,9 +30,11 @@ const ProcedureSchema = z.object({
|
||||
})
|
||||
|
||||
const OperationExecutionSchema = z.object({
|
||||
surgeryType: z.enum(['kecil', 'sedang', 'besar', 'khusus']),
|
||||
billingCode: z.string().min(1),
|
||||
operationSystem: z.enum(['khusus', 'cito', 'elektif']),
|
||||
surgeryType: z.enum(['kecil', 'sedang', 'besar', 'khusus'], { required_error: 'Silahkan pilih jenis operasi' }),
|
||||
billingCode: z.string({
|
||||
required_error: 'Silahkan pilih kode billing',
|
||||
}),
|
||||
operationSystem: z.enum(['khusus', 'cito', 'efektif', 'urgent'], { required_error: 'Silahkan pilih sistem operasi' }),
|
||||
|
||||
operationStartAt: isoDateTime,
|
||||
operationEndAt: isoDateTime,
|
||||
@@ -41,17 +43,19 @@ const OperationExecutionSchema = z.object({
|
||||
anesthesiaEndAt: isoDateTime,
|
||||
|
||||
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(),
|
||||
babyWeightGram: positiveInt.optional(),
|
||||
personWeight: positiveInt.optional(),
|
||||
birthCondition: z.string().optional(),
|
||||
|
||||
operationDescription: z.string().min(1),
|
||||
operationDescription: z.string({
|
||||
required_error: 'Mohon lengkapi uraian operasi',
|
||||
}),
|
||||
|
||||
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({
|
||||
@@ -74,11 +78,17 @@ const ImplantSchema = z.object({
|
||||
})
|
||||
|
||||
const SpecimenSchema = z.object({
|
||||
destination: z.string().min(1),
|
||||
destination: z.string({
|
||||
required_error: 'Silahkan pilih specimen',
|
||||
}),
|
||||
})
|
||||
|
||||
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({
|
||||
|
||||
Reference in New Issue
Block a user