Merge branch 'dev' of https://github.com/dikstub-rssa/simrs-fe into feat/adj-enc-list-199
This commit is contained in:
@@ -0,0 +1,468 @@
|
||||
<script setup lang="ts">
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { type Duration, intervalToDuration } from 'date-fns'
|
||||
|
||||
// schema
|
||||
import { type ActionReportFormData, ActionReportSchema } from '~/schemas/action-report.schema'
|
||||
|
||||
// type
|
||||
import type { Doctor } from '~/models/doctor'
|
||||
import { type ClickType as ActionClickType } from '~/components/pub/my-ui/nav-footer/index'
|
||||
|
||||
// components
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
import Separator from '~/components/pub/ui/separator/Separator.vue'
|
||||
import { ArrayMessage } from '~/components/pub/ui/form'
|
||||
import ActionForm from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
||||
|
||||
// form field components
|
||||
import {
|
||||
FillNotes,
|
||||
RadioBloods,
|
||||
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
|
||||
|
||||
// #region Props & Emits
|
||||
interface FormData extends ActionReportFormData {
|
||||
_operationDuration: string
|
||||
_anesthesiaDuration: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
isLoading: boolean
|
||||
mode: 'add' | 'edit' | 'view'
|
||||
initialValues?: Partial<FormData>
|
||||
|
||||
// form related
|
||||
doctors: Doctor[]
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit', payload: FormData): void
|
||||
(e: 'back'): void
|
||||
(e: 'error', errors: Error): void
|
||||
}>()
|
||||
|
||||
const tissueNotesLimit = 5
|
||||
const mode = toRef(props, 'mode')
|
||||
const isLoading = toRef(props, 'isLoading')
|
||||
|
||||
const isReadonly = computed(() => {
|
||||
if (isLoading.value === true) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (mode.value === 'view') {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
const formSchema = toTypedSchema(ActionReportSchema)
|
||||
|
||||
const { errors, handleSubmit, values, meta, resetForm, setFieldValue, setValues, validate } = useForm<FormData>({
|
||||
name: 'encounterActionReportForm',
|
||||
validationSchema: formSchema,
|
||||
initialValues: props.initialValues ? props.initialValues : {},
|
||||
validateOnMount: false,
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
resetForm,
|
||||
setValues,
|
||||
values,
|
||||
})
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
// #endregion
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
// #endregion
|
||||
|
||||
// #region Functions
|
||||
// #endregion region
|
||||
|
||||
// #region Utilities & event handlers
|
||||
const onFormActionClicked = (action: ActionClickType) => {
|
||||
if (action === 'back') {
|
||||
emit('back')
|
||||
return
|
||||
}
|
||||
if (action === 'submit') {
|
||||
onSubmit()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = handleSubmit(
|
||||
(values) => {
|
||||
console.log(JSON.stringify(values))
|
||||
emit('submit', values)
|
||||
},
|
||||
(errors) => {
|
||||
console.error(errors)
|
||||
emit('error', new Error('Silahkan lengkapi form terlebih dahulu'))
|
||||
},
|
||||
)
|
||||
// #endregion
|
||||
|
||||
// #region Watcher
|
||||
watch(
|
||||
() => [values.operationExecution.operationStartAt, values.operationExecution.operationEndAt],
|
||||
([start, end]) => {
|
||||
if (!start || !end) return
|
||||
const pStart = new Date(start)
|
||||
const pEnd = new Date(end)
|
||||
|
||||
const formatTime = (r: Duration) =>
|
||||
[r.hours && `${r.hours} jam`, r.minutes && `${r.minutes} menit`, r.seconds && `${r.seconds} detik`]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
|
||||
const res = intervalToDuration({
|
||||
start: pStart,
|
||||
end: pEnd,
|
||||
})
|
||||
|
||||
setFieldValue('_operationDuration', formatTime(res))
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => [values.operationExecution.anesthesiaStartAt, values.operationExecution.anesthesiaEndAt],
|
||||
([start, end]) => {
|
||||
if (!start || !end) return
|
||||
const pStart = new Date(start)
|
||||
const pEnd = new Date(end)
|
||||
|
||||
const formatTime = (r: Duration) =>
|
||||
[r.hours && `${r.hours} jam`, r.minutes && `${r.minutes} menit`, r.seconds && `${r.seconds} detik`]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
|
||||
const res = intervalToDuration({
|
||||
start: pStart,
|
||||
end: pEnd,
|
||||
})
|
||||
|
||||
setFieldValue('_anesthesiaDuration', formatTime(res))
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
// #endregion
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<Fragment
|
||||
v-slot="{ section }"
|
||||
title="Tim Pelaksana Tindakan"
|
||||
>
|
||||
<p class="text-lg font-semibold">{{ section }}</p>
|
||||
|
||||
<DE.Block
|
||||
:col-count="4"
|
||||
:cell-flex="false"
|
||||
>
|
||||
<SelectDoctor
|
||||
fieldName="operatorTeam.dpjpId"
|
||||
label="Dokter Pemeriksa"
|
||||
placeholder="Pilih dokter"
|
||||
:doctors="doctors"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="operatorTeam.operatorName"
|
||||
label="Operator"
|
||||
placeholder="Masukkan operator"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="operatorTeam.assistantOperatorName"
|
||||
label="Asisten Operator"
|
||||
placeholder="Masukkan asisten operator"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="operatorTeam.instrumentNurseName"
|
||||
label="Instrumentir"
|
||||
placeholder="Masukkan instrumentir"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="operatorTeam.surgeryDate"
|
||||
label="Tanggal Pembedahan"
|
||||
input-type="datetime-local"
|
||||
:is-disabled="isReadonly"
|
||||
placeholder=""
|
||||
/>
|
||||
</DE.Block>
|
||||
<DE.Block
|
||||
:col-count="4"
|
||||
:cell-flex="false"
|
||||
>
|
||||
<TextAreaInput
|
||||
field-name="operatorTeam.actionDiagnosis"
|
||||
label="Diagnosa Tindakan"
|
||||
placeholder="Masukkan diagnosa tindakan"
|
||||
:col-span="2"
|
||||
:rows="5"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="operatorTeam.postSurgeryNurseId"
|
||||
label="Perawat Pasca Bedah"
|
||||
placeholder="Masukkan perawat pasca bedah"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
</DE.Block>
|
||||
</Fragment>
|
||||
|
||||
<Separator class="my-4" />
|
||||
|
||||
<Fragment
|
||||
v-slot="{ section }"
|
||||
title="Tindakan Operatif/Non Operatif Lain"
|
||||
>
|
||||
<!-- <p class="text-lg font-semibold">{{ section }}</p> -->
|
||||
<DE.Block
|
||||
:col-count="2"
|
||||
:cell-flex="false"
|
||||
>
|
||||
<DE.Cell>
|
||||
<slot name="procedures" />
|
||||
<ArrayMessage
|
||||
class="mt-1"
|
||||
v-if="meta.touched"
|
||||
name="procedures"
|
||||
/>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
</Fragment>
|
||||
|
||||
<Separator class="my-4" />
|
||||
|
||||
<Fragment
|
||||
v-slot="{ section }"
|
||||
title="Data Pelaksanaan Operasi"
|
||||
>
|
||||
<p class="text-lg font-semibold">{{ section }}</p>
|
||||
|
||||
<DE.Block
|
||||
:col-count="3"
|
||||
:cell-flex="false"
|
||||
>
|
||||
<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"
|
||||
input-type="datetime-local"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="operationExecution.operationEndAt"
|
||||
label="Operasi Selesai"
|
||||
placeholder="Pilih Tanggal"
|
||||
input-type="datetime-local"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="_operationDuration"
|
||||
label="Lama Operasi"
|
||||
placeholder="-"
|
||||
is-disabled
|
||||
/>
|
||||
</DE.Block>
|
||||
|
||||
<DE.Block
|
||||
:col-count="3"
|
||||
:cell-flex="false"
|
||||
>
|
||||
<InputBase
|
||||
field-name="operationExecution.anesthesiaStartAt"
|
||||
label="Pembiusan Mulai"
|
||||
placeholder="Pilih Tanggal"
|
||||
input-type="datetime-local"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="operationExecution.anesthesiaEndAt"
|
||||
label="Pembiusan Selesai"
|
||||
placeholder="Pilih Tanggal"
|
||||
input-type="datetime-local"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="_anesthesiaDuration"
|
||||
label="Lama Pembiusan"
|
||||
placeholder="-"
|
||||
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"
|
||||
>
|
||||
<RadioBloods
|
||||
field-name="bloodInput"
|
||||
label="Jenis & Jumlah Darah Masuk"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
</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">
|
||||
<ActionForm @click="onFormActionClicked" />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -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,10 @@
|
||||
export { default as FillNotes } from './fill-notes.vue'
|
||||
export { default as RadioBloods } from './radio-bloods.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,101 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||
import { Label as RadioLabel } from '~/components/pub/ui/label'
|
||||
import { Input } from '~/components/pub/ui/input'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
label: string
|
||||
class?: string
|
||||
radioGroupClass?: string
|
||||
radioItemClass?: string
|
||||
labelClass?: string
|
||||
isDisabled?: boolean
|
||||
}>()
|
||||
|
||||
const { class: containerClass, radioGroupClass, radioItemClass, labelClass } = props
|
||||
|
||||
const opts = [
|
||||
{ label: 'PRC', value: 'prc' },
|
||||
{ label: 'WB', value: 'wb' },
|
||||
{ label: 'FFP', value: 'ffp' },
|
||||
{ label: 'TC', value: 'tc' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DE.Cell
|
||||
:class="cn('radio-group-field', containerClass)"
|
||||
:col-span="2"
|
||||
>
|
||||
<DE.Label :label-for="fieldName">
|
||||
{{ label }}
|
||||
</DE.Label>
|
||||
<DE.Field :id="fieldName">
|
||||
<FormField
|
||||
v-slot="{ componentField: radioField }"
|
||||
:name="`${fieldName}.type`"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
:model-value="radioField.modelValue"
|
||||
@update:model-value="radioField.onChange"
|
||||
:class="cn('grid grid-cols-1 items-start gap-4 sm:grid-cols-2 ', radioGroupClass)"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in opts"
|
||||
:key="option.value"
|
||||
:class="cn('grid grid-cols-1 items-start gap-3 sm:grid-cols-[auto,1fr]', radioItemClass)"
|
||||
>
|
||||
<div class="flex min-w-fit items-center gap-2">
|
||||
<RadioGroupItem
|
||||
:id="`type-${index}`"
|
||||
:value="option.value"
|
||||
:disabled="isDisabled"
|
||||
/>
|
||||
<RadioLabel :for="`type-${index}`">
|
||||
{{ option.label }}
|
||||
</RadioLabel>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
v-slot="{ componentField: amountField }"
|
||||
:name="`${fieldName}.amount.${option.value}`"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div class="relative w-[140px]">
|
||||
<Input
|
||||
v-bind="amountField"
|
||||
placeholder="00"
|
||||
class="pr-10"
|
||||
:disabled="radioField.modelValue !== option.value || isDisabled"
|
||||
@input="
|
||||
(e: InputEvent) => {
|
||||
const target = e.target as HTMLInputElement
|
||||
const v = target.value.replace(/\D/g, '')
|
||||
amountField.onChange(v)
|
||||
}
|
||||
"
|
||||
/>
|
||||
<span
|
||||
class="absolute inset-y-0 end-0 flex items-center justify-center px-2 text-sm text-muted-foreground"
|
||||
>
|
||||
CC
|
||||
</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</template>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,88 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import { format } from 'date-fns'
|
||||
import { id } from 'date-fns/locale'
|
||||
|
||||
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
|
||||
import type { ActionReportData } from '~/components/app/action-report/sample'
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-d.vue'))
|
||||
|
||||
export const config: Config = {
|
||||
cols: [
|
||||
{ width: 120 },
|
||||
{ width: 120 },
|
||||
{ width: 120 },
|
||||
{ width: 120 },
|
||||
{ width: 120 },
|
||||
{ width: 120 },
|
||||
{ width: 120 },
|
||||
{ width: 50 },
|
||||
],
|
||||
|
||||
headers: [
|
||||
[
|
||||
{ label: 'TANGGAL LAPORAN' },
|
||||
{ label: 'DPJP' },
|
||||
{ label: 'OPERATOR' },
|
||||
{ label: 'TANGGAL PEMBEDAHAN' },
|
||||
{ label: 'JENIS OPERASI' },
|
||||
{ label: 'KODE BILLING' },
|
||||
{ label: 'SISTEM OPERASI' },
|
||||
{ label: 'AKSI' },
|
||||
],
|
||||
],
|
||||
|
||||
keys: ['reportAt', 'dpjp', 'operator', 'operationAt', 'operationType', 'billing', 'system', 'action'],
|
||||
|
||||
delKeyNames: [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
],
|
||||
|
||||
parses: {
|
||||
reportAt: (rec: unknown): unknown => {
|
||||
const attr = (rec as ActionReportData).reportAt
|
||||
const result = format(new Date(attr), 'd MMMM yyyy, HH:mm', { locale: id })
|
||||
|
||||
return result
|
||||
},
|
||||
operationAt: (rec: unknown): unknown => {
|
||||
const attr = (rec as ActionReportData).operationAt
|
||||
const result = format(new Date(attr), 'd MMMM yyyy', { locale: id })
|
||||
|
||||
return result
|
||||
},
|
||||
system: (rec: unknown): unknown => {
|
||||
return 'Cito'
|
||||
},
|
||||
operator: (rec: unknown): unknown => {
|
||||
return 'dr. Dewi Arum Sawitri, Sp.An'
|
||||
},
|
||||
billing: (rec: unknown): unknown => {
|
||||
return 'General'
|
||||
},
|
||||
operationType: (rec: unknown): unknown => {
|
||||
return 'Besar'
|
||||
},
|
||||
dpjp: (rec: unknown): unknown => {
|
||||
return 'dr. Irwansyah Kurniawan Sp.Bo'
|
||||
},
|
||||
parent: (rec: unknown): unknown => {
|
||||
const recX = rec as any
|
||||
return recX.parent?.name || '-'
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
}
|
||||
return res
|
||||
},
|
||||
},
|
||||
|
||||
htmls: {},
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { config } from './list.cfg'
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<PubMyUiDataTable
|
||||
v-bind="config"
|
||||
:rows="data"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,89 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import { format } from 'date-fns'
|
||||
import { id } from 'date-fns/locale'
|
||||
|
||||
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
|
||||
import type { ActionReportData } from '~/components/app/action-report/sample'
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const config: Config = {
|
||||
cols: [
|
||||
{ width: 120 },
|
||||
{ width: 120 },
|
||||
{ width: 120 },
|
||||
{ width: 120 },
|
||||
{ width: 120 },
|
||||
{ width: 120 },
|
||||
{ width: 120 },
|
||||
{ width: 50 },
|
||||
],
|
||||
|
||||
headers: [
|
||||
[
|
||||
{ label: 'TANGGAL LAPORAN' },
|
||||
{ label: 'DPJP' },
|
||||
{ label: 'OPERATOR' },
|
||||
{ label: 'TANGGAL PEMBEDAHAN' },
|
||||
{ label: 'JENIS OPERASI' },
|
||||
{ label: 'KODE BILLING' },
|
||||
{ label: 'SISTEM OPERASI' },
|
||||
{ label: 'AKSI' },
|
||||
],
|
||||
],
|
||||
|
||||
keys: ['reportAt', 'dpjp', 'operator', 'operationAt', 'operationType', 'billing', 'system', 'action'],
|
||||
|
||||
delKeyNames: [
|
||||
{ key: 'id', label: 'ID' },
|
||||
{ key: 'dokter', label: 'Dokter' },
|
||||
{ key: 'reportAt', label: 'Tanggal Laporan' },
|
||||
],
|
||||
|
||||
parses: {
|
||||
reportAt: (rec: unknown): unknown => {
|
||||
const attr = (rec as ActionReportData).reportAt
|
||||
const result = format(new Date(attr), 'd MMMM yyyy, HH:mm', { locale: id })
|
||||
|
||||
return result
|
||||
},
|
||||
operationAt: (rec: unknown): unknown => {
|
||||
const attr = (rec as ActionReportData).operationAt
|
||||
const result = format(new Date(attr), 'd MMMM yyyy', { locale: id })
|
||||
|
||||
return result
|
||||
},
|
||||
system: (rec: unknown): unknown => {
|
||||
return 'Cito'
|
||||
},
|
||||
operator: (rec: unknown): unknown => {
|
||||
return 'dr. Dewi Arum Sawitri, Sp.An'
|
||||
},
|
||||
billing: (rec: unknown): unknown => {
|
||||
return 'General'
|
||||
},
|
||||
operationType: (rec: unknown): unknown => {
|
||||
return 'Besar'
|
||||
},
|
||||
dpjp: (rec: unknown): unknown => {
|
||||
return 'dr. Irwansyah Kurniawan Sp.Bo'
|
||||
},
|
||||
parent: (rec: unknown): unknown => {
|
||||
const recX = rec as any
|
||||
return recX.parent?.name || '-'
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
}
|
||||
return res
|
||||
},
|
||||
},
|
||||
|
||||
htmls: {},
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { config } from './list.cfg'
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<PubMyUiDataTable
|
||||
v-bind="config"
|
||||
:rows="data"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,166 @@
|
||||
<script setup lang="ts">
|
||||
import { format } from 'date-fns'
|
||||
import { id } from 'date-fns/locale'
|
||||
|
||||
// type
|
||||
import { type ProcedureSrc } from '~/models/procedure-src'
|
||||
import { type ActionReportFormData } from '~/schemas/action-report.schema'
|
||||
|
||||
// componenets
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '~/components/pub/ui/accordion'
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
import DetailRow from '~/components/pub/my-ui/form/view/detail-row.vue'
|
||||
import ArrangementProcedurePicker from '~/components/app/therapy-protocol/picker-dialog/arrangement-procedure/procedure-picker.vue'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
data: ActionReportFormData
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'back'): void
|
||||
(e: 'edit'): void
|
||||
}>()
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
const { operatorTeam, procedures, operationExecution, bloodInput, implant, specimen, tissueNotes = [] } = props.data
|
||||
|
||||
const procedureSampleData = procedures as unknown as ProcedureSrc[]
|
||||
// #region Lifecycle Hooks
|
||||
// #endregion
|
||||
|
||||
// #region Functions
|
||||
|
||||
// #endregion region
|
||||
// #region Utilities & event handlers
|
||||
function onNavigate(type: string) {
|
||||
if (type == 'back') emit('back')
|
||||
if (type == 'edit') emit('edit')
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region Watchers
|
||||
// #endregion
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DetailRow label="Tanggal Laporan">
|
||||
{{ format(new Date(), 'd MMMM yyyy, HH:mm', { locale: id }) }}
|
||||
</DetailRow>
|
||||
|
||||
<Accordion
|
||||
type="multiple"
|
||||
class="w-full"
|
||||
collapsible
|
||||
:default-value="['section-1', 'section-2', 'section-3']"
|
||||
>
|
||||
<AccordionItem value="section-1">
|
||||
<AccordionTrigger>Tim Pelaksanaan Tindakan</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<DE.Block
|
||||
:cell-flex="false"
|
||||
:col-count="2"
|
||||
>
|
||||
<DE.Cell>
|
||||
<DetailRow label="DPJP">dr. Marcell Galliard Sp.Gr</DetailRow>
|
||||
<DetailRow label="Operator">Sumitro</DetailRow>
|
||||
<DetailRow label="Asisten Operator">Alexis Lewis Carol</DetailRow>
|
||||
<DetailRow label="Instrumentir">Mikel Arteta</DetailRow>
|
||||
<DetailRow label="Tanggal Pembedahan">
|
||||
{{ format(new Date(), 'd MMMM yyyy', { locale: id }) }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Diagnosa Tindakan">{{ operatorTeam?.actionDiagnosis || '-' }}</DetailRow>
|
||||
<DetailRow label="Perawat Pasca Bedah">Cak Armuji</DetailRow>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="section-2">
|
||||
<AccordionTrigger>Tindakan Operatif / Non Operatif Lain</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<DE.Block
|
||||
:cell-flex="false"
|
||||
:col-count="2"
|
||||
>
|
||||
<DE.Cell>
|
||||
<ArrangementProcedurePicker
|
||||
field-name="procedures"
|
||||
title="List Prosedur"
|
||||
sub-title="Pilih Prosedur"
|
||||
:mode="'preview'"
|
||||
:sample-items="procedureSampleData"
|
||||
/>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="section-3">
|
||||
<AccordionTrigger>Data Pelaksanaan Tindakan</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<DE.Block
|
||||
:cell-flex="false"
|
||||
:col-count="2"
|
||||
>
|
||||
<DE.Cell>
|
||||
<DetailRow label="Jenis Operasi">dr. Marcell Galliard Sp.Gr</DetailRow>
|
||||
<DetailRow label="Kode Billing">GCASH1128190</DetailRow>
|
||||
<DetailRow label="Sistem Operasi">Alexis Lewis Carol</DetailRow>
|
||||
<DetailRow label="Operasi Mulai">
|
||||
{{ format(new Date(), 'd MMMM yyyy, HH:mm', { locale: id }) }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Operasi Selesai">
|
||||
{{ format(new Date(), 'd MMMM yyyy, HH:mm', { locale: id }) }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Lama Operasi">5 menit</DetailRow>
|
||||
<DetailRow label="Pembiusan Mulai">
|
||||
{{ format(new Date(), 'd MMMM yyyy, HH:mm', { locale: id }) }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Pembiusan Selesai">
|
||||
{{ format(new Date(), 'd MMMM yyyy, HH:mm', { locale: id }) }}
|
||||
</DetailRow>
|
||||
<DetailRow label="Lama Pembiusan">5 menit</DetailRow>
|
||||
|
||||
<DetailRow label="PRC">300 CC</DetailRow>
|
||||
<DetailRow label="FPP">-</DetailRow>
|
||||
<DetailRow label="WB">-</DetailRow>
|
||||
<DetailRow label="TC">-</DetailRow>
|
||||
<DetailRow label="Merk">-</DetailRow>
|
||||
<DetailRow label="Nama Implant">-</DetailRow>
|
||||
<DetailRow label="Sticker / Nomor Register Implant">-</DetailRow>
|
||||
<DetailRow label="Nama Pendamping Implant">-</DetailRow>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DetailRow label="Jenis Pembedahan">Bersih</DetailRow>
|
||||
<DetailRow label="Operasi ke">1 (Satu)</DetailRow>
|
||||
<DetailRow label="Keterangan Lahir">Lahir Hidup</DetailRow>
|
||||
<DetailRow label="Ket. Tempat Lahir">RSSA</DetailRow>
|
||||
<DetailRow label="Berat Badan">18 gram</DetailRow>
|
||||
<DetailRow label="Ket. Saat Lahir">Normal dan sehat</DetailRow>
|
||||
<DetailRow label="Uraian Operasi">-</DetailRow>
|
||||
<DetailRow label="Jumlah Pendarahan">300 CC</DetailRow>
|
||||
<DetailRow label="Specimen / Jaringan dikirim ke">PA</DetailRow>
|
||||
<DetailRow label="Keterangan Jaringan">
|
||||
<ul
|
||||
class="list-disc space-y-1 pl-5 text-sm"
|
||||
v-if="tissueNotes.length > 0"
|
||||
v-for="item in tissueNotes"
|
||||
>
|
||||
<li>{{ item.note }}</li>
|
||||
</ul>
|
||||
<span v-else>-</span>
|
||||
</DetailRow>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<div class="my-2 flex justify-end py-2">
|
||||
<PubMyUiNavFooterBaEd @click="onNavigate" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,54 @@
|
||||
import { addWeeks, formatISO } from 'date-fns'
|
||||
|
||||
export type ActionReportData = {
|
||||
id: number
|
||||
reportAt: string
|
||||
operationAt: string
|
||||
noRm: string
|
||||
noBill: string
|
||||
nama: string
|
||||
jk: string
|
||||
alamat: string
|
||||
klinik: string
|
||||
dokter: string
|
||||
caraBayar: string
|
||||
rujukan: string
|
||||
ketRujukan: string
|
||||
asal: string
|
||||
}
|
||||
|
||||
export const sampleRows: ActionReportData[] = [
|
||||
{
|
||||
id: 1,
|
||||
reportAt: formatISO(addWeeks(new Date(), -1)),
|
||||
operationAt: formatISO(addWeeks(new Date(), 1)),
|
||||
noRm: 'RM23311224',
|
||||
noBill: '-',
|
||||
nama: 'Ahmad Baidowi',
|
||||
jk: 'L',
|
||||
alamat: 'Jl Jaksa Agung S. No. 9',
|
||||
klinik: 'Penyakit dalam',
|
||||
dokter: 'Dr. Andreas Sutaji',
|
||||
caraBayar: 'JKN',
|
||||
rujukan: 'Faskes BPJS',
|
||||
ketRujukan: 'RUMAH SAKIT - RS Lawang Medika - Malang',
|
||||
asal: 'Rawat Jalan Reguler',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
reportAt: new Date().toISOString(),
|
||||
operationAt: formatISO(addWeeks(new Date(), 2)),
|
||||
noRm: 'RM23455667',
|
||||
noBill: '-',
|
||||
nama: 'Abraham Sulaiman',
|
||||
jk: 'L',
|
||||
alamat: 'Purwantoro, Blimbing',
|
||||
klinik: 'Penyakit dalam',
|
||||
dokter: 'Dr. Andreas Sutaji',
|
||||
caraBayar: 'JKN',
|
||||
rujukan: 'Faskes BPJS',
|
||||
ketRujukan: 'RUMAH SAKIT - RS Lawang Medika - Malang',
|
||||
asal: 'Rawat Jalan Reguler',
|
||||
},
|
||||
// tambahkan lebih banyak baris contoh jika perlu
|
||||
]
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const config: Config = {
|
||||
cols: [{}, {}, {}, { width: 50 }],
|
||||
|
||||
headers: [[
|
||||
{ label: 'Tgl. Order' },
|
||||
{ label: 'No. Order' },
|
||||
{ label: 'Jadwal Pemeriksaan' },
|
||||
{ label: 'Lokalisasi' },
|
||||
{ label: 'Stadium' },
|
||||
{ label: 'Status' },
|
||||
{ label: 'Resume' },
|
||||
{ label: '' }]],
|
||||
|
||||
keys: [
|
||||
'date',
|
||||
'number',
|
||||
'examinationDate',
|
||||
'localization',
|
||||
'stadium',
|
||||
'resume',
|
||||
'',
|
||||
],
|
||||
|
||||
delKeyNames: [
|
||||
{ key: 'date', label: 'Tanggal' },
|
||||
{ key: 'number', label: 'Nomor' },
|
||||
],
|
||||
|
||||
parses: {
|
||||
parent: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.parent?.name || '-'
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
props: {
|
||||
size: 'sm',
|
||||
},
|
||||
}
|
||||
return res
|
||||
},
|
||||
},
|
||||
|
||||
htmls: {},
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import DataTable from '~/components/pub/my-ui/data-table/data-table.vue'
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { config } from './list.cfg'
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<DataTable
|
||||
v-bind="config"
|
||||
:rows="data"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,215 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import { FieldArray, useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
// types
|
||||
// import { type AssessmentEducationFormData, AssessmentEducationSchema, encode } from '~/schemas/assessment-education'
|
||||
|
||||
// componenets
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
import Separator from '~/components/pub/ui/separator/Separator.vue'
|
||||
import { BaseSelect, CheckboxGeneral, CheckboxSpecial } from './fields'
|
||||
import { ButtonAction, TextAreaInput } from '~/components/pub/my-ui/form'
|
||||
import ActionForm from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
||||
|
||||
// constant
|
||||
import {
|
||||
abilityCode,
|
||||
willCode,
|
||||
medObstacleCode,
|
||||
learnMethodCode,
|
||||
langClassCode,
|
||||
translatorSrcCode,
|
||||
} from '~/lib/clinical.constants'
|
||||
|
||||
interface FormData {}
|
||||
interface Props {
|
||||
noteLimit?: number
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
mode: 'add' | 'edit' | 'view'
|
||||
initialValues?: Partial<FormData>
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isLoading: false,
|
||||
isReadonly: false,
|
||||
noteLimit: 10,
|
||||
})
|
||||
const isDisabled = computed(() => props.isLoading || props.isReadonly)
|
||||
// const formSchema = toTypedSchema(AssessmentEducationSchema)
|
||||
const formSchema = toTypedSchema(z.object({}))
|
||||
const { errors, handleSubmit, values, meta, resetForm, setFieldValue, setValues, validate } = useForm<FormData>({
|
||||
name: 'assessmentEducationForm',
|
||||
validationSchema: formSchema,
|
||||
initialValues: props.initialValues
|
||||
? props.initialValues
|
||||
: {
|
||||
plans: [
|
||||
{
|
||||
id: 1,
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
validateOnMount: false,
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
resetForm,
|
||||
setValues,
|
||||
values,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent="">
|
||||
<p class="mb-2 text-sm font-semibold 2xl:mb-3 2xl:text-base">Kebutuhan Edukasi</p>
|
||||
<DE.Block
|
||||
:col-count="4"
|
||||
:cell-flex="false"
|
||||
>
|
||||
<CheckboxGeneral
|
||||
field-name="generalEducationNeeds"
|
||||
label="Informasi Umum"
|
||||
:col-span="2"
|
||||
:is-disabled="isDisabled"
|
||||
/>
|
||||
<CheckboxSpecial
|
||||
field-name="specificEducationNeeds"
|
||||
label="Edukasi Khusus"
|
||||
:col-span="2"
|
||||
:is-disabled="isDisabled"
|
||||
/>
|
||||
</DE.Block>
|
||||
|
||||
<div class="h-6">
|
||||
<Separator />
|
||||
</div>
|
||||
|
||||
<p class="mb-2 text-sm font-semibold 2xl:mb-3 2xl:text-base">Asesmen Kemampuan dan Kemauan Belajar</p>
|
||||
<DE.Block
|
||||
:col-count="3"
|
||||
:cell-flex="false"
|
||||
>
|
||||
<BaseSelect
|
||||
field-name="learningAbility"
|
||||
label="Kemampuan Belajar"
|
||||
:items="abilityCode"
|
||||
:is-disabled="isDisabled"
|
||||
/>
|
||||
|
||||
<BaseSelect
|
||||
field-name="learningWillingness"
|
||||
label="Kemauan Belajar"
|
||||
:items="willCode"
|
||||
:is-disabled="isDisabled"
|
||||
/>
|
||||
|
||||
<BaseSelect
|
||||
field-name="barrier"
|
||||
label="Hambatan"
|
||||
:items="medObstacleCode"
|
||||
:is-disabled="isDisabled"
|
||||
/>
|
||||
|
||||
<BaseSelect
|
||||
field-name="learningMethod"
|
||||
label="Metode Pembelajaran"
|
||||
:items="learnMethodCode"
|
||||
:is-disabled="isDisabled"
|
||||
/>
|
||||
|
||||
<BaseSelect
|
||||
field-name="language"
|
||||
label="Bahasa"
|
||||
:items="langClassCode"
|
||||
:is-disabled="isDisabled"
|
||||
/>
|
||||
|
||||
<BaseSelect
|
||||
field-name="languageBarrier"
|
||||
label="Hambatan Bahasa"
|
||||
:items="translatorSrcCode"
|
||||
:is-disabled="isDisabled"
|
||||
/>
|
||||
|
||||
<BaseSelect
|
||||
field-name="beliefValue"
|
||||
label="Keyakinan pada Nilai-Nilai yang Dianut"
|
||||
:items="{
|
||||
ya: 'IYA',
|
||||
tidak: 'TIDAK',
|
||||
}"
|
||||
:is-disabled="isDisabled"
|
||||
/>
|
||||
</DE.Block>
|
||||
<div class="h-6">
|
||||
<Separator />
|
||||
</div>
|
||||
|
||||
<p class="mb-2 text-sm font-semibold 2xl:mb-3 2xl:text-base">Rencana Edukasi</p>
|
||||
<DE.Block
|
||||
:col-count="1"
|
||||
:cell-flex="false"
|
||||
>
|
||||
<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="6"
|
||||
:cell-flex="false"
|
||||
>
|
||||
<DE.Cell :col-span="5">
|
||||
<TextAreaInput
|
||||
:field-name="`plans[${idx}].value`"
|
||||
:label="'Rencana ' + Number(idx + 1)"
|
||||
placeholder="Masukkan rencana catatan"
|
||||
:col-span="2"
|
||||
:rows="5"
|
||||
:is-disabled="isReadonly"
|
||||
/>
|
||||
</DE.Cell>
|
||||
<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 Rencana ${idx + 1}`"
|
||||
icon-only
|
||||
@click="remove(idx)"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
</div>
|
||||
|
||||
<div class="self-center pt-3">
|
||||
<ButtonAction
|
||||
preset="add"
|
||||
label="Tambah Rencana"
|
||||
title="Tambah Rencana Edukasi"
|
||||
:disabled="fields.length >= noteLimit || isReadonly"
|
||||
:full-width-mobile="true"
|
||||
class="mt-4"
|
||||
@click="push({ id: fields.length + 1, value: '' })"
|
||||
/>
|
||||
</div>
|
||||
</FieldArray>
|
||||
</DE.Block>
|
||||
<!-- todo -->
|
||||
<div class="mt-4 flex justify-end">
|
||||
<ActionForm @click="() => {}" />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
import { Checkbox } from '~/components/pub/ui/checkbox'
|
||||
|
||||
import type { CheckItem } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
label: string
|
||||
fieldName: string
|
||||
items: CheckItem[]
|
||||
isDisabled?: boolean
|
||||
colSpan?: number
|
||||
}>()
|
||||
|
||||
const { label, fieldName, items } = props
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DE.Cell :col-span="colSpan || 1">
|
||||
<DE.Label :label-for="fieldName">
|
||||
{{ label }}
|
||||
</DE.Label>
|
||||
|
||||
<DE.Field :id="fieldName">
|
||||
<FormField
|
||||
:name="fieldName"
|
||||
v-slot="{ value, handleChange }"
|
||||
>
|
||||
<FormItem>
|
||||
<div
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
class="ml-1 flex flex-row items-start space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
:disabled="isDisabled"
|
||||
:id="`cb-${fieldName}-${item.id}`"
|
||||
:checked="value?.includes(item.id)"
|
||||
@update:checked="
|
||||
(checked) => {
|
||||
const newValue = [...(value || [])]
|
||||
if (checked) {
|
||||
if (!newValue.includes(item.id)) newValue.push(item.id)
|
||||
} else {
|
||||
const idx = newValue.indexOf(item.id)
|
||||
if (idx > -1) newValue.splice(idx, 1)
|
||||
}
|
||||
handleChange(newValue)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel
|
||||
:for="`cb-${fieldName}-${item.id}`"
|
||||
class="font-normal leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 md:!text-xs 2xl:text-sm"
|
||||
>
|
||||
{{ item.label }}
|
||||
</FormLabel>
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</template>
|
||||
@@ -0,0 +1,79 @@
|
||||
<script setup lang="ts">
|
||||
import Select from '~/components/pub/my-ui/form/select.vue'
|
||||
import { cn, mapToComboboxOptList } from '~/lib/utils'
|
||||
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
label: string
|
||||
items: Record<string, string>
|
||||
placeholder?: string
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const { placeholder = 'Pilih', class: containerClass, selectClass, fieldGroupClass, labelClass } = props
|
||||
|
||||
const opts = computed(() => {
|
||||
const data = mapToComboboxOptList(props.items)
|
||||
// hide code on select options
|
||||
const filtered = data
|
||||
.filter((item) => item.code)
|
||||
.reduce(
|
||||
(acc, item) => {
|
||||
acc.push({
|
||||
label: item.label as string,
|
||||
value: item.value as string,
|
||||
})
|
||||
return acc
|
||||
},
|
||||
[] as Array<{ label: string; value: string }>,
|
||||
)
|
||||
|
||||
return filtered
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||
<DE.Label
|
||||
:label-for="fieldName"
|
||||
:class="cn('select-field-label', labelClass)"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ label }}
|
||||
</DE.Label>
|
||||
<DE.Field
|
||||
:id="fieldName"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select
|
||||
:id="fieldName"
|
||||
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,25 @@
|
||||
<script setup lang="ts">
|
||||
import BaseCheckbox from './base-checkbox.vue'
|
||||
import { generalEduCode } from '~/lib/clinical.constants'
|
||||
import { mapToCheckItems } from '~/lib/utils'
|
||||
|
||||
interface Props {
|
||||
label: string
|
||||
fieldName: string
|
||||
isDisabled?: boolean
|
||||
colSpan?: number
|
||||
}
|
||||
defineProps<Props>()
|
||||
|
||||
const generalItems = mapToCheckItems(generalEduCode)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseCheckbox
|
||||
:field-name="fieldName"
|
||||
:label="label"
|
||||
:is-disabled="isDisabled"
|
||||
:col-span="colSpan"
|
||||
:items="generalItems"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import BaseCheckbox from './base-checkbox.vue'
|
||||
import { specialEduCode } from '~/lib/clinical.constants'
|
||||
import { mapToCheckItems } from '~/lib/utils'
|
||||
interface Props {
|
||||
label: string
|
||||
fieldName: string
|
||||
colSpan?: number
|
||||
}
|
||||
defineProps<Props>()
|
||||
|
||||
const specialItems = mapToCheckItems(specialEduCode)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseCheckbox
|
||||
:field-name="fieldName"
|
||||
:label="label"
|
||||
:col-span="colSpan"
|
||||
:items="specialItems"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,4 @@
|
||||
export { default as BaseSelect } from './base-select.vue'
|
||||
export { default as CheckboxGeneral } from './checkbox-general.vue'
|
||||
export { default as CheckboxSpecial } from './checkbox-special.vue'
|
||||
export { default as SelectAssessmentCode } from './select-assessment-code.vue'
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
|
||||
const props = defineProps<{
|
||||
label: string
|
||||
fieldName: string
|
||||
placeholder?: string
|
||||
colSpan?: number
|
||||
}>()
|
||||
|
||||
const { label, fieldName, placeholder = 'Masukkan catatan' } = props
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DE.Cell :col-span="colSpan || 1">
|
||||
<DE.Label :label-for="fieldName">
|
||||
{{ label }}
|
||||
</DE.Label>
|
||||
<DE.Field :id="fieldName">
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
v-bind="componentField"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</template>
|
||||
@@ -0,0 +1,75 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import { format } from 'date-fns'
|
||||
import { id } from 'date-fns/locale'
|
||||
|
||||
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
|
||||
import type { ActionReportData } from '~/components/app/action-report/sample'
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const config: Config = {
|
||||
cols: [{ width: 120 }, { width: 120 }, { width: 120 }, { width: 120 }, { width: 120 }, { width: 50 }],
|
||||
|
||||
headers: [
|
||||
[
|
||||
{ label: 'TANGGAL' },
|
||||
{ label: 'INFORMASI UMUM' },
|
||||
{ label: 'EDUKASI KHUSUS' },
|
||||
{ label: 'RENCANA EDUKASI' },
|
||||
{ label: 'PELAKSANAAN' },
|
||||
{ label: 'AKSI' },
|
||||
],
|
||||
],
|
||||
|
||||
keys: ['reportAt', 'dpjp', 'operator', 'operationAt', 'operationType', 'action'],
|
||||
|
||||
delKeyNames: [
|
||||
{ key: 'id', label: 'ID' },
|
||||
{ key: 'dokter', label: 'Dokter' },
|
||||
{ key: 'reportAt', label: 'Tanggal Laporan' },
|
||||
],
|
||||
|
||||
parses: {
|
||||
reportAt: (rec: unknown): unknown => {
|
||||
const attr = (rec as ActionReportData).reportAt
|
||||
const result = format(new Date(attr), 'd MMMM yyyy, HH:mm', { locale: id })
|
||||
|
||||
return result
|
||||
},
|
||||
operationAt: (rec: unknown): unknown => {
|
||||
return '1 Rencana Edukasi'
|
||||
},
|
||||
system: (rec: unknown): unknown => {
|
||||
return 'Cito'
|
||||
},
|
||||
operator: (rec: unknown): unknown => {
|
||||
return '2 Edukasi dipilih'
|
||||
},
|
||||
billing: (rec: unknown): unknown => {
|
||||
return 'General'
|
||||
},
|
||||
operationType: (rec: unknown): unknown => {
|
||||
return '-'
|
||||
},
|
||||
dpjp: (rec: unknown): unknown => {
|
||||
return '3 Informasi Dipilih'
|
||||
},
|
||||
parent: (rec: unknown): unknown => {
|
||||
const recX = rec as any
|
||||
return recX.parent?.name || '-'
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
}
|
||||
return res
|
||||
},
|
||||
},
|
||||
|
||||
htmls: {},
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { config } from './list.cfg'
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<PubMyUiDataTable
|
||||
v-bind="config"
|
||||
:rows="data"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,75 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import { format } from 'date-fns'
|
||||
import { id } from 'date-fns/locale'
|
||||
|
||||
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
|
||||
import type { ActionReportData } from '~/components/app/action-report/sample'
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const config: Config = {
|
||||
cols: [{ width: 120 }, { width: 120 }, { width: 120 }, { width: 120 }, { width: 120 }, { width: 50 }],
|
||||
|
||||
headers: [
|
||||
[
|
||||
{ label: 'TANGGAL' },
|
||||
{ label: 'INFORMASI UMUM' },
|
||||
{ label: 'EDUKASI KHUSUS' },
|
||||
{ label: 'RENCANA EDUKASI' },
|
||||
{ label: 'PELAKSANAAN' },
|
||||
{ label: 'AKSI' },
|
||||
],
|
||||
],
|
||||
|
||||
keys: ['reportAt', 'dpjp', 'operator', 'operationAt', 'operationType', 'action'],
|
||||
|
||||
delKeyNames: [
|
||||
{ key: 'id', label: 'ID' },
|
||||
{ key: 'dokter', label: 'Dokter' },
|
||||
{ key: 'reportAt', label: 'Tanggal Laporan' },
|
||||
],
|
||||
|
||||
parses: {
|
||||
reportAt: (rec: unknown): unknown => {
|
||||
const attr = (rec as ActionReportData).reportAt
|
||||
const result = format(new Date(attr), 'd MMMM yyyy, HH:mm', { locale: id })
|
||||
|
||||
return result
|
||||
},
|
||||
operationAt: (rec: unknown): unknown => {
|
||||
return '1 Rencana Edukasi'
|
||||
},
|
||||
system: (rec: unknown): unknown => {
|
||||
return 'Cito'
|
||||
},
|
||||
operator: (rec: unknown): unknown => {
|
||||
return '2 Edukasi dipilih'
|
||||
},
|
||||
billing: (rec: unknown): unknown => {
|
||||
return 'General'
|
||||
},
|
||||
operationType: (rec: unknown): unknown => {
|
||||
return '-'
|
||||
},
|
||||
dpjp: (rec: unknown): unknown => {
|
||||
return '3 Informasi Dipilih'
|
||||
},
|
||||
parent: (rec: unknown): unknown => {
|
||||
const recX = rec as any
|
||||
return recX.parent?.name || '-'
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
}
|
||||
return res
|
||||
},
|
||||
},
|
||||
|
||||
htmls: {},
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { config } from './list.cfg'
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<PubMyUiDataTable
|
||||
v-bind="config"
|
||||
:rows="data"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,54 @@
|
||||
import { addWeeks, formatISO } from 'date-fns'
|
||||
|
||||
export type ActionReportData = {
|
||||
id: number
|
||||
reportAt: string
|
||||
operationAt: string
|
||||
noRm: string
|
||||
noBill: string
|
||||
nama: string
|
||||
jk: string
|
||||
alamat: string
|
||||
klinik: string
|
||||
dokter: string
|
||||
caraBayar: string
|
||||
rujukan: string
|
||||
ketRujukan: string
|
||||
asal: string
|
||||
}
|
||||
|
||||
export const sampleRows: ActionReportData[] = [
|
||||
{
|
||||
id: 1,
|
||||
reportAt: formatISO(addWeeks(new Date(), -1)),
|
||||
operationAt: formatISO(addWeeks(new Date(), 1)),
|
||||
noRm: 'RM23311224',
|
||||
noBill: '-',
|
||||
nama: 'Ahmad Baidowi',
|
||||
jk: 'L',
|
||||
alamat: 'Jl Jaksa Agung S. No. 9',
|
||||
klinik: 'Penyakit dalam',
|
||||
dokter: 'Dr. Andreas Sutaji',
|
||||
caraBayar: 'JKN',
|
||||
rujukan: 'Faskes BPJS',
|
||||
ketRujukan: 'RUMAH SAKIT - RS Lawang Medika - Malang',
|
||||
asal: 'Rawat Jalan Reguler',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
reportAt: new Date().toISOString(),
|
||||
operationAt: formatISO(addWeeks(new Date(), 2)),
|
||||
noRm: 'RM23455667',
|
||||
noBill: '-',
|
||||
nama: 'Abraham Sulaiman',
|
||||
jk: 'L',
|
||||
alamat: 'Purwantoro, Blimbing',
|
||||
klinik: 'Penyakit dalam',
|
||||
dokter: 'Dr. Andreas Sutaji',
|
||||
caraBayar: 'JKN',
|
||||
rujukan: 'Faskes BPJS',
|
||||
ketRujukan: 'RUMAH SAKIT - RS Lawang Medika - Malang',
|
||||
asal: 'Rawat Jalan Reguler',
|
||||
},
|
||||
// tambahkan lebih banyak baris contoh jika perlu
|
||||
]
|
||||
@@ -33,7 +33,7 @@ const {
|
||||
const units = ref<Array<Item>>([])
|
||||
|
||||
async function fetchData() {
|
||||
units.value = await getUnitLabelList({}, true)
|
||||
units.value = await getUnitLabelList({})
|
||||
}
|
||||
|
||||
const selectedUnitId = inject<Ref<string | null>>("selectedUnitId")!
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default as SelectDoctor } from './select-doctor.vue'
|
||||
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
// type
|
||||
import type { Doctor } from '~/models/doctor'
|
||||
import { type Person, parseName } from '~/models/person'
|
||||
|
||||
// componenets
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
label: string
|
||||
placeholder: string
|
||||
doctors?: Doctor[]
|
||||
class?: string
|
||||
selectClass?: string
|
||||
fieldGroupClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
isDisabled?: boolean
|
||||
colSpan?: number
|
||||
}>()
|
||||
|
||||
const { class: containerClass, labelClass, colSpan = 1, doctors = [] } = props
|
||||
|
||||
const opts = computed(() => {
|
||||
return doctors.map((doc) => ({
|
||||
value: doc.id,
|
||||
label: parseName(doc.employee.person as Person),
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DE.Cell
|
||||
:col-span="colSpan"
|
||||
:class="cn('select-field-group', fieldGroupClass, containerClass)"
|
||||
>
|
||||
<DE.Label
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ label }}
|
||||
</DE.Label>
|
||||
<DE.Field
|
||||
:id="fieldName"
|
||||
:class="cn('select-field-wrapper')"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Combobox
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="opts"
|
||||
:placeholder="placeholder"
|
||||
:is-disabled="isDisabled"
|
||||
search-placeholder="Cari dokter..."
|
||||
empty-message="Dokter tidak ditemukan"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</template>
|
||||
@@ -85,10 +85,8 @@ const isExcluded = (key: string) => props.excludeFields?.includes(key)
|
||||
</Cell>
|
||||
</Block>
|
||||
|
||||
<Separator class="mt-8" />
|
||||
|
||||
<div class="my-2 flex items-center justify-between">
|
||||
<h1 class="font-semibold">Anggota Keluarga</h1>
|
||||
<div class="text-sm 2xl:text-base font-semibold">Anggota Keluarga</div>
|
||||
<Button
|
||||
type="button"
|
||||
@click="addRelative"
|
||||
@@ -102,7 +100,7 @@ const isExcluded = (key: string) => props.excludeFields?.includes(key)
|
||||
:key="idx"
|
||||
class="my-2 rounded-md border border-slate-300 p-4"
|
||||
>
|
||||
<Block :colCount="2">
|
||||
<Block :colCount="2" :cell-flex="false" class="!mb-2">
|
||||
<Cell>
|
||||
<Label dynamic>Nama Anggota Keluarga</Label>
|
||||
<Field>
|
||||
@@ -118,97 +116,83 @@ const isExcluded = (key: string) => props.excludeFields?.includes(key)
|
||||
</Cell>
|
||||
</Block>
|
||||
|
||||
<button
|
||||
<Button
|
||||
variant="destructive"
|
||||
type="button"
|
||||
class="mt-3 text-sm text-red-500"
|
||||
@click="removeRelative(idx)"
|
||||
>
|
||||
Hapus
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Separator class="mt-8" />
|
||||
|
||||
<!-- Responsible Section -->
|
||||
<div class="my-2">
|
||||
<h1 class="font-semibold">Penanggung Jawab</h1>
|
||||
<div class="mt-8 mb-1.5">
|
||||
<div class="text-sm 2xl:text-base font-semibold">Penanggung Jawab</div>
|
||||
</div>
|
||||
<Block :col-count="2" :cell-flex="false">
|
||||
<Cell>
|
||||
<Label dynamic>Nama Penanggung Jawab</Label>
|
||||
<Field>
|
||||
<Input
|
||||
v-model="responsibleName"
|
||||
v-bind="responsibleNameAttrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<div class="my-2 rounded-md border border-slate-300 p-4">
|
||||
<Block :colCount="2">
|
||||
<Cell>
|
||||
<Label dynamic>Nama Penanggung Jawab</Label>
|
||||
<Field>
|
||||
<Input
|
||||
v-model="responsibleName"
|
||||
v-bind="responsibleNameAttrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>No. Hp Penanggung Jawab</Label>
|
||||
<Field>
|
||||
<Input
|
||||
v-model="responsiblePhone"
|
||||
v-bind="responsiblePhoneAttrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
</div>
|
||||
|
||||
<Separator class="mt-8" />
|
||||
<Cell>
|
||||
<Label dynamic>No. Hp Penanggung Jawab</Label>
|
||||
<Field>
|
||||
<Input
|
||||
v-model="responsiblePhone"
|
||||
v-bind="responsiblePhoneAttrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
|
||||
<!-- Informant -->
|
||||
<div class="my-2">
|
||||
<h1 class="font-semibold">Pemberi Informasi</h1>
|
||||
<div class="mt-8 mb-1.5">
|
||||
<div class="text-sm 2xl:text-base font-semibold">Pemberi Informasi</div>
|
||||
</div>
|
||||
<Block :col-count="2" :cell-flex="false">
|
||||
<Cell>
|
||||
<Label dynamic>Informant</Label>
|
||||
<Field>
|
||||
<Input
|
||||
v-model="informant"
|
||||
v-bind="informantAttrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
|
||||
<div class="my-2 rounded-md border border-slate-300 p-4">
|
||||
<Block>
|
||||
<Cell>
|
||||
<Label dynamic>Informant</Label>
|
||||
<Field>
|
||||
<Input
|
||||
v-model="informant"
|
||||
v-bind="informantAttrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
</div>
|
||||
|
||||
<Separator class="mt-8" />
|
||||
|
||||
<!-- Witnesses -->
|
||||
<div class="my-2">
|
||||
<h1 class="font-semibold">Saksi</h1>
|
||||
<div class="mt-8 mb-1.5">
|
||||
<div class="text-sm 2xl:text-base font-semibold">Saksi</div>
|
||||
</div>
|
||||
<Block :col-count="2" :cell-flex="false">
|
||||
<Cell>
|
||||
<Label dynamic>Saksi 1</Label>
|
||||
<Field>
|
||||
<Input
|
||||
v-model="witness1"
|
||||
v-bind="witness1Attrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<div class="my-2 rounded-md border border-slate-300 p-4">
|
||||
<Block :colCount="2">
|
||||
<Cell>
|
||||
<Label dynamic>Saksi 1</Label>
|
||||
<Field>
|
||||
<Input
|
||||
v-model="witness1"
|
||||
v-bind="witness1Attrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
<Label dynamic>Saksi 2</Label>
|
||||
<Field>
|
||||
<Input
|
||||
v-model="witness2"
|
||||
v-bind="witness2Attrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
</div>
|
||||
<Cell>
|
||||
<Label dynamic>Saksi 2</Label>
|
||||
<Field>
|
||||
<Input
|
||||
v-model="witness2"
|
||||
v-bind="witness2Attrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@@ -6,7 +6,7 @@ type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-ud.vue'))
|
||||
export const config: Config = {
|
||||
cols: [{ width: 100 }, {}, {}, {}, { width: 50 }],
|
||||
cols: [{ width: 100 }, {}, {}, {}, {}, {}, { width: 50 }],
|
||||
headers: [
|
||||
[
|
||||
{ label: 'Tanggal' },
|
||||
|
||||
@@ -13,20 +13,33 @@ const props = defineProps<{
|
||||
Order {{ data?.createdAt?.substring(0, 10) }} - {{ data?.status_code }}
|
||||
</div>
|
||||
<div class="max-w-[1000px]">
|
||||
<DE.Block mode="preview" :col-count=5 class="!mb-3">
|
||||
<DE.Block :col-count=5 class="!mb-3" :cell-flex="false">
|
||||
<DE.Cell :col-span="2">
|
||||
<DE.Label class="font-semibold">DPJP</DE.Label>
|
||||
<DE.Label>DPJP</DE.Label>
|
||||
<DE.Field>
|
||||
{{ data?.doctor?.employee?.person?.name || '.........' }}
|
||||
<Input :value="data?.doctor?.employee?.person?.name || '.........'" readonly />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell></DE.Cell>
|
||||
<DE.Cell :col-span="2">
|
||||
<DE.Label class="font-semibold">PPDS</DE.Label>
|
||||
<DE.Label>PPDS</DE.Label>
|
||||
<DE.Field>
|
||||
...........
|
||||
<Input :value="'.........'" readonly />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell :col-span="2">
|
||||
<DE.Label>Pemeriksaan Ke</DE.Label>
|
||||
<DE.Field>
|
||||
<Input readonly />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell></DE.Cell>
|
||||
<DE.Cell :col-span="2">
|
||||
<DE.Label>Temperatur Aksiler</DE.Label>
|
||||
<DE.Field>
|
||||
<Input />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
<script setup lang="ts">
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
import * as Cbx from '~/components/pub/my-ui/checkboxes'
|
||||
import { fi } from 'date-fns/locale';
|
||||
|
||||
const substances = [
|
||||
{ value: 'biopsi', label: 'Biopsi'},
|
||||
{ value: 'operation', label: 'Operasi' },
|
||||
{ value: 'scraping', label: 'Kerokan' },
|
||||
{ value: 'cytology', label: 'Sitologi' },
|
||||
{ value: 'fnab', label: 'FNAB' },
|
||||
]
|
||||
|
||||
const fications = [
|
||||
{ value: 'pa-formaline-10p', label: 'pA. Formalin 10%' },
|
||||
{ value: 'spt-alcohol-70p', label: 'Sputum Alkohol 70%' },
|
||||
{ value: 'urn-alcohol-50p', label: 'Urine Alkohol 50%' },
|
||||
{ value: 'vgn-smr-alcohol-95p', label: 'Vagibal Smear 95%' },
|
||||
]
|
||||
|
||||
const prevAp = [
|
||||
{ value: 'clinical', label: 'Klinik' },
|
||||
{ value: 'ro', label: 'RO' },
|
||||
{ value: 'clinical-pat', label: 'Pat. Klinik' },
|
||||
{ value: 'operation', label: 'Operasi' },
|
||||
{ value: 'necropsy', label: 'Nekropsi' },
|
||||
]
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="header">Data Order</div>
|
||||
<DE.Block :col-count="3" :cell-flex="false">
|
||||
<DE.Cell>
|
||||
<DE.Label>Tgl. Order</DE.Label>
|
||||
<DE.Field>
|
||||
<Input readonly />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>DPJP</DE.Label>
|
||||
<DE.Field>
|
||||
<Input readonly />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>PPDS</DE.Label>
|
||||
<DE.Field>
|
||||
<Input readonly />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<div class="header">Bahan</div>
|
||||
<DE.Block :cell-flex="false">
|
||||
<DE.Cell>
|
||||
<DE.Field>
|
||||
<Cbx.Checkboxes :items="substances" :use-flex="true" />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<div class="header">Fiksasi</div>
|
||||
<DE.Block :cell-flex="false">
|
||||
<DE.Cell>
|
||||
<DE.Field>
|
||||
<Cbx.Checkboxes :items="fications" :use-flex="true" />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<div class="header">Data Klinis Pasien</div>
|
||||
<DE.Block :col-count="6" :cell-flex="false">
|
||||
<DE.Cell :col-span="3">
|
||||
<DE.Label>Lokalisasi</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell :col-span="3">
|
||||
<DE.Label>Diagnosa Klinik</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>Stadium T</DE.Label>
|
||||
<DE.Field>
|
||||
<Input />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>Stadium M</DE.Label>
|
||||
<DE.Field>
|
||||
<Input />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>Stadium N</DE.Label>
|
||||
<DE.Field>
|
||||
<Input />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<div class=""></div>
|
||||
<DE.Cell :col-span="3">
|
||||
<DE.Label>Keterangan Klinik</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea rows="2" />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<div class="header">Riwayat Pemeriksaan</div>
|
||||
<DE.Block :col-count="2" :cell-flex="false">
|
||||
<DE.Cell>
|
||||
<DE.Label>Riwayat Dulu</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>Riwayat Sekarang</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>Pemeriksaan PA Sebelumnya</DE.Label>
|
||||
<DE.Field>
|
||||
<Cbx.Checkboxes :items="prevAp" :use-flex="true" />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<div></div>
|
||||
<DE.Cell>
|
||||
<DE.Label>Keterangan PA Sebelumnya</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>Pemeriksaan Penunjang</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
|
||||
</DE.Block>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.header {
|
||||
@apply mb-1.5 text-sm 2xl:text-base font-semibold
|
||||
}
|
||||
</style>
|
||||
@@ -39,7 +39,7 @@ defineExpose({
|
||||
<div>
|
||||
<p
|
||||
v-if="props.title"
|
||||
class="text-sm 2xl:text-base mb-2 2xl:mb-3 font-semibold"
|
||||
class="mb-2 text-sm font-semibold 2xl:mb-3 2xl:text-base"
|
||||
>
|
||||
{{ props.title || 'Kontak Pasien' }}
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
<script setup lang="ts">
|
||||
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
|
||||
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
|
||||
// Helpers
|
||||
import type z from 'zod'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
// import { ref, watch, inject } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: any
|
||||
schema: z.ZodSchema<any>
|
||||
excludeFields?: string[]
|
||||
isReadonly?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', val: any): void
|
||||
(e: 'submit', val: any): void
|
||||
}>()
|
||||
|
||||
// Setup form
|
||||
const {
|
||||
validate: _validate,
|
||||
defineField,
|
||||
handleSubmit,
|
||||
errors,
|
||||
values,
|
||||
} = useForm({
|
||||
validationSchema: toTypedSchema(props.schema),
|
||||
initialValues: props.modelValue,
|
||||
})
|
||||
|
||||
watch(values, (val) => emit('update:modelValue', val), { deep: true })
|
||||
|
||||
// Define form fields
|
||||
// const [relatives, relativesAttrs] = defineField('relatives')
|
||||
const [date, dateAttrs] = defineField('date')
|
||||
const [doctor, doctorAttrs] = defineField('doctor')
|
||||
const [diagnosis, diagnosisAttrs] = defineField('diagnosis')
|
||||
const [essay, essayAttrs] = defineField('essay')
|
||||
const [plan, planAttrs] = defineField('plan')
|
||||
const [note, noteAttrs] = defineField('note')
|
||||
|
||||
const tanggal = ref('')
|
||||
const tanggalAttrs = ref({
|
||||
type: 'date',
|
||||
required: true,
|
||||
disabled: props.isReadonly,
|
||||
})
|
||||
|
||||
// Relatives list handling
|
||||
const addRelative = () => {
|
||||
relatives.value = [...(relatives.value || []), { name: '', phone: '' }]
|
||||
}
|
||||
|
||||
const removeRelative = (index: number) => {
|
||||
relatives.value = relatives.value.filter((_: any, i: number) => i !== index)
|
||||
}
|
||||
|
||||
const validate = async () => {
|
||||
const result = await _validate()
|
||||
return {
|
||||
valid: true,
|
||||
data: result.values,
|
||||
errors: result.errors,
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ validate })
|
||||
|
||||
const icdPreview = inject('icdPreview')
|
||||
|
||||
const isExcluded = (key: string) => props.excludeFields?.includes(key)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="entry-form">
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<Block :colCount="2">
|
||||
<Cell>
|
||||
<Label dynamic>Tanggal</Label>
|
||||
<Field>
|
||||
<Input
|
||||
v-model="date"
|
||||
v-bind="dateAttrs"
|
||||
type="date"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label dynamic>Dokter</Label>
|
||||
<Field>
|
||||
<Input
|
||||
v-model="doctor"
|
||||
v-bind="doctorAttrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<Block :colCount="2">
|
||||
<Cell>
|
||||
<Label dynamic>Diagnosis Penting</Label>
|
||||
<Field>
|
||||
<Textarea
|
||||
v-model="diagnosis"
|
||||
v-bind="diagnosisAttrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label dynamic>Uraian Klinik Penting</Label>
|
||||
<Field>
|
||||
<Textarea
|
||||
v-model="essay"
|
||||
v-bind="essayAttrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<Block :colCount="2">
|
||||
<Cell>
|
||||
<Label dynamic>Rencana Penting</Label>
|
||||
<Field>
|
||||
<Textarea
|
||||
v-model="plan"
|
||||
v-bind="planAttrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label dynamic>Catatan</Label>
|
||||
<Field>
|
||||
<Textarea
|
||||
v-model="note"
|
||||
v-bind="noteAttrs"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
|
||||
<Separator class="mt-8" />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,82 @@
|
||||
import type { Config, RecComponent, RecStrFuncComponent, RecStrFuncUnknown } from '~/components/pub/my-ui/data-table'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { GeneralConsent } from '~/models/general-consent'
|
||||
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-ud.vue'))
|
||||
export const config: Config = {
|
||||
cols: [{ width: 100 }, {}, {}, {}, { width: 50 }],
|
||||
headers: [
|
||||
[
|
||||
{ label: 'Tanggal' },
|
||||
{ label: 'Anggota Keluarga' },
|
||||
{ label: 'Penanggung Jawab' },
|
||||
{ label: 'Pemberi Informasi' },
|
||||
{ label: 'Saksi 1' },
|
||||
{ label: 'Saksi 2' },
|
||||
{ label: '' },
|
||||
],
|
||||
],
|
||||
keys: ['date', 'relatives', 'responsible', 'informant', 'witness1', 'witness2', 'action'],
|
||||
delKeyNames: [
|
||||
{ key: 'data', label: 'Tanggal' },
|
||||
{ key: 'dstDoctor.name', label: 'Dokter' },
|
||||
],
|
||||
parses: {
|
||||
date(rec) {
|
||||
const recX = rec as GeneralConsent
|
||||
return recX?.createdAt?.substring(0, 10) || '-'
|
||||
},
|
||||
relatives(rec) {
|
||||
const recX = rec as GeneralConsent
|
||||
const parsed = JSON.parse(recX?.value || '{}')
|
||||
return parsed?.relatives?.join(', ') || '-'
|
||||
},
|
||||
responsible(rec) {
|
||||
const recX = rec as GeneralConsent
|
||||
const parsed = JSON.parse(recX?.value || '{}')
|
||||
return parsed?.responsible || '-'
|
||||
},
|
||||
informant(rec) {
|
||||
const recX = rec as GeneralConsent
|
||||
const parsed = JSON.parse(recX?.value || '{}')
|
||||
return parsed?.informant || '-'
|
||||
},
|
||||
witness1(rec) {
|
||||
const recX = rec as GeneralConsent
|
||||
const parsed = JSON.parse(recX?.value || '{}')
|
||||
return parsed?.witness1 || '-'
|
||||
},
|
||||
witness2(rec) {
|
||||
const recX = rec as GeneralConsent
|
||||
const parsed = JSON.parse(recX?.value || '{}')
|
||||
return parsed?.witness2 || '-'
|
||||
},
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
props: {
|
||||
size: 'sm',
|
||||
},
|
||||
}
|
||||
return res
|
||||
},
|
||||
},
|
||||
components: {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
props: {
|
||||
size: 'sm',
|
||||
},
|
||||
}
|
||||
return res
|
||||
},
|
||||
} as RecStrFuncComponent,
|
||||
htmls: {} as RecStrFuncUnknown,
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
import { config } from './list.cfg'
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<PubMyUiDataTable
|
||||
v-bind="config"
|
||||
:rows="data"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<!-- FIXME: pindahkan ke content/division/list.vue -->
|
||||
<PaginationView
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -42,7 +42,7 @@ const itemsCount = computed(() => items.length || 0)
|
||||
{{ item?.createdAt.toLocaleDateString('id-ID') }}
|
||||
</time>
|
||||
<h1 :class="cn('text-gray-500 dark:text-gray-400', '')">Ditambahkan Oleh : {{ item.updatedBy }}</h1>
|
||||
<NuxtLink class="mt-1 text-orange-500" :to="`surgery-report/${item.id}`">
|
||||
<NuxtLink class="mt-1 text-orange-500" :to="`process?menu=surgery-report&mode=list&record-id=${item.id}`">
|
||||
Lihat Detail
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
+19
-14
@@ -45,6 +45,7 @@ const {
|
||||
handleSearch,
|
||||
fetchData: getItemList,
|
||||
} = usePaginatedList({
|
||||
syncToUrl: false,
|
||||
fetchFn: async (params: any) => {
|
||||
const result = await getList({
|
||||
search: params.search,
|
||||
@@ -102,19 +103,23 @@ onMounted(async () => {
|
||||
</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
|
||||
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>
|
||||
|
||||
+124
-43
@@ -1,62 +1,143 @@
|
||||
<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'
|
||||
// Types
|
||||
import { type ProcedureSrc } from '~/models/procedure-src'
|
||||
|
||||
// Helper
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
// Components
|
||||
import { FieldArray } from 'vee-validate'
|
||||
import { ButtonAction } from '~/components/pub/my-ui/form'
|
||||
import TableHeader from '~/components/pub/ui/table/TableHeader.vue'
|
||||
import { is } from 'date-fns/locale'
|
||||
import ProcedureListDialog from './procedure-list.vue'
|
||||
|
||||
interface Props {
|
||||
fieldName: string
|
||||
title: string
|
||||
subTitle?: string
|
||||
|
||||
// State UI (Loading / Disabled)
|
||||
isReadonly?: boolean
|
||||
|
||||
// Data Architecture Switch
|
||||
// 'form' = Pakai Vee-Validate (Parent wajib useForm)
|
||||
// 'preview' = Pakai Props sampleItems (Parent bebas)
|
||||
mode?: 'form' | 'preview'
|
||||
|
||||
// Data Source untuk mode 'preview' (atau initial data)
|
||||
sampleItems?: ProcedureSrc[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// Set default mode ke 'form' agar backward compatible
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
mode: 'form',
|
||||
isReadonly: false,
|
||||
sampleItems: () => [],
|
||||
})
|
||||
|
||||
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 class="mb-2 flex items-center justify-between">
|
||||
<p class="mb-2 font-medium">{{ title }}</p>
|
||||
|
||||
<ButtonAction
|
||||
v-if="mode === 'form' && !isReadonly"
|
||||
preset="add"
|
||||
title="Tambah Item"
|
||||
icon="i-lucide-search"
|
||||
:label="subTitle || 'Pilih Diagnosis'"
|
||||
:full-width-mobile="true"
|
||||
@click="isProcedurePickerDialogOpen = true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FieldArray
|
||||
v-if="mode === 'form'"
|
||||
v-slot="{ fields, push, remove }"
|
||||
:name="props.fieldName"
|
||||
>
|
||||
<ProcedureListDialog :process-fn="push" />
|
||||
|
||||
<div class="overflow-hidden rounded-lg border border-gray-200">
|
||||
<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-[50px]">Action</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-if="fields.length === 0">
|
||||
<TableCell
|
||||
colspan="3"
|
||||
class="py-4 text-center text-muted-foreground"
|
||||
>
|
||||
Belum ada data dipilih.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
v-for="(field, idx) in fields"
|
||||
:key="field.key"
|
||||
>
|
||||
<TableCell :class="cn(isReadonly && 'opacity-50')">
|
||||
{{ (field.value as ProcedureSrc)?.name }}
|
||||
</TableCell>
|
||||
<TableCell :class="cn(isReadonly && 'opacity-50')">
|
||||
{{ (field.value as ProcedureSrc)?.code }}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<ButtonAction
|
||||
v-if="!isReadonly"
|
||||
preset="delete"
|
||||
icon-only
|
||||
:title="`Hapus ${(field.value as ProcedureSrc)?.name}`"
|
||||
@click="remove(idx)"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</FieldArray>
|
||||
|
||||
<FieldArray v-slot="{ fields, push, remove }" :name="props.fieldName">
|
||||
<ProcedureListDialog :process-fn="push" />
|
||||
<div
|
||||
v-else
|
||||
class="overflow-hidden rounded-lg border border-gray-200"
|
||||
>
|
||||
<Table>
|
||||
<TableHeader class="bg-gray-100">
|
||||
<TableRow>
|
||||
<TableHead class="w-1/2">Prosedur</TableHead>
|
||||
<TableHead class="w-1/2">ICD-X</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-if="sampleItems.length === 0">
|
||||
<TableCell
|
||||
colspan="2"
|
||||
class="py-4 text-center text-muted-foreground"
|
||||
>
|
||||
Tidak ada data.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<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>
|
||||
<TableRow
|
||||
v-for="item in sampleItems"
|
||||
:key="item.code || item.id"
|
||||
>
|
||||
<TableCell class="text-muted-foreground">
|
||||
{{ item.name }}
|
||||
</TableCell>
|
||||
<TableCell class="text-muted-foreground">
|
||||
{{ item.code }}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import List from './list.vue'
|
||||
import Form from './form.vue'
|
||||
import View from './view.vue'
|
||||
|
||||
// Models
|
||||
import type { Encounter } from '~/models/encounter'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
encounter: Encounter
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { mode, goToEntry, goToView } = useQueryCRUDMode('mode')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<List
|
||||
v-if="mode === 'list'"
|
||||
:encounter="props.encounter"
|
||||
@add="goToEntry"
|
||||
@edit="goToEntry({ fromView: false })"
|
||||
@view="goToView"
|
||||
/>
|
||||
<View
|
||||
v-else-if="mode === 'view'"
|
||||
:encounter="props.encounter"
|
||||
/>
|
||||
<Form v-else />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
import mockData from './sample'
|
||||
|
||||
// type
|
||||
import { genDoctor, type Doctor } from '~/models/doctor'
|
||||
import type { ActionReportFormData } from '~/schemas/action-report.schema'
|
||||
|
||||
// components
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
import AppActionReportEntry from '~/components/app/action-report/entry-form.vue'
|
||||
import ArrangementProcedurePicker from '~/components/app/therapy-protocol/picker-dialog/arrangement-procedure/procedure-picker.vue'
|
||||
|
||||
// states
|
||||
const route = useRoute()
|
||||
const { mode, goBack } = useQueryCRUDMode('mode')
|
||||
const { recordId } = useQueryCRUDRecordId('record-id')
|
||||
const reportData = ref<ActionReportFormData>({} as unknown as ActionReportFormData)
|
||||
const doctors = ref<Doctor[]>([])
|
||||
const isLoading = ref<boolean>(false)
|
||||
|
||||
// TODO: dummy data
|
||||
;(() => {
|
||||
doctors.value = [genDoctor()]
|
||||
})()
|
||||
|
||||
const entryMode = ref<'add' | 'edit' | 'view'>('add')
|
||||
const isDataReady = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
if (mode.value === 'entry' && recordId.value) {
|
||||
entryMode.value = 'edit'
|
||||
await loadEntryForEdit(+recordId.value)
|
||||
} else {
|
||||
// Untuk mode 'add', langsung set ready
|
||||
isDataReady.value = true
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: map data
|
||||
async function loadEntryForEdit(id: number | string) {
|
||||
isLoading.value = true
|
||||
const result = mockData
|
||||
reportData.value = result as ActionReportFormData
|
||||
isLoading.value = false
|
||||
isDataReady.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppActionReportEntry
|
||||
v-if="isDataReady"
|
||||
:isLoading="isLoading"
|
||||
:mode="entryMode"
|
||||
@submit="(val) => console.log(val)"
|
||||
@back="goBack"
|
||||
@error="
|
||||
(err: Error) => {
|
||||
toast({
|
||||
title: 'Terjadi Kesalahan',
|
||||
description: err.message,
|
||||
variant: 'destructive',
|
||||
})
|
||||
}
|
||||
"
|
||||
:doctors="doctors"
|
||||
:initialValues="reportData"
|
||||
>
|
||||
<template #procedures>
|
||||
<ArrangementProcedurePicker
|
||||
field-name="procedures"
|
||||
title="Tindakan Operatif/Non-Operatif Lain"
|
||||
sub-title="Pilih Prosedur"
|
||||
/>
|
||||
</template>
|
||||
</AppActionReportEntry>
|
||||
<div
|
||||
v-else
|
||||
class="flex items-center justify-center p-8"
|
||||
>
|
||||
<p class="text-muted-foreground">Memuat data...</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,276 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import AppActionReportList from '~/components/app/action-report/list.vue'
|
||||
import AppActionReportListHistory from '~/components/app/action-report/list-history.vue'
|
||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||
import { ButtonAction } from '~/components/pub/my-ui/form'
|
||||
|
||||
// config
|
||||
import { config } from '~/components/app/action-report/list.cfg'
|
||||
|
||||
// types
|
||||
import { ActionEvents } from '~/components/pub/my-ui/data/types'
|
||||
import type { Encounter } from '~/models/encounter'
|
||||
|
||||
// Samples
|
||||
import { sampleRows, type ActionReportData } from '~/components/app/action-report/sample'
|
||||
import sampleReport from './sample'
|
||||
|
||||
// helpers
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
encounter: Encounter
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const emits = defineEmits<{
|
||||
(e: 'add'): void
|
||||
(e: 'edit', id: number | string): void
|
||||
(e: 'view', id: number | string): void
|
||||
}>()
|
||||
|
||||
// states
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { goToEntry, backToList } = useQueryCRUDMode('mode')
|
||||
const title = ref('')
|
||||
const search = ref('')
|
||||
const dateFrom = ref('')
|
||||
const dateTo = ref('')
|
||||
const isDialogOpen = ref<boolean>(false)
|
||||
const isLoading = ref<boolean>(false)
|
||||
|
||||
// #region mock
|
||||
// Handlers
|
||||
import {
|
||||
recId,
|
||||
recAction,
|
||||
recItem,
|
||||
isReadonly,
|
||||
isProcessing,
|
||||
isFormEntryDialogOpen,
|
||||
isRecordConfirmationOpen,
|
||||
onResetState,
|
||||
handleActionSave,
|
||||
handleActionEdit,
|
||||
handleActionRemove,
|
||||
handleCancelForm,
|
||||
} from '~/handlers/consultation.handler'
|
||||
// #endregion
|
||||
|
||||
// filter + pencarian sederhana (client-side)
|
||||
const filtered = computed(() => {
|
||||
const q = search.value.trim().toLowerCase()
|
||||
return sampleRows.filter((r: ActionReportData) => {
|
||||
if (q) {
|
||||
return r.nama.toLowerCase().includes(q) || r.noRm.toLowerCase().includes(q) || r.dokter.toLowerCase().includes(q)
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
const goEdit = (id: number | string) => {
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
...route.query,
|
||||
mode: 'entry',
|
||||
'record-id': id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const goView = (id: number | string) => {
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
...route.query,
|
||||
mode: 'view',
|
||||
'record-id': id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
|
||||
async function onGetDetail(id: number | string) {
|
||||
isLoading.value = true
|
||||
const res = sampleReport
|
||||
recItem.value = res
|
||||
console.log(res)
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
// #region watcher
|
||||
watch([recId, recAction], (newVal) => {
|
||||
const [id, action] = newVal
|
||||
|
||||
// Guard: jangan proses jika id = 0 atau action kosong
|
||||
if (!id || !action) return
|
||||
|
||||
switch (action) {
|
||||
case ActionEvents.showDetail:
|
||||
// onGetDetail(recId.value)
|
||||
goView(id)
|
||||
title.value = 'Detail Konsultasi'
|
||||
break
|
||||
case ActionEvents.showEdit:
|
||||
goEdit(id)
|
||||
title.value = 'Edit Konsultasi'
|
||||
break
|
||||
case ActionEvents.showConfirmDelete:
|
||||
isRecordConfirmationOpen.value = true
|
||||
break
|
||||
}
|
||||
|
||||
// Reset KEDUANYA menggunakan nextTick agar tidak trigger watcher lagi
|
||||
nextTick(() => {
|
||||
recId.value = 0
|
||||
recAction.value = ''
|
||||
})
|
||||
})
|
||||
// #endregion
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto max-w-full">
|
||||
<div class="border-b p-6">
|
||||
<h1 class="text-2xl font-semibold">Laporan Tindakan</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Infomasi laporan tindakan pasien</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3 border-b p-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="search"
|
||||
placeholder="Cari Nama / No.RM"
|
||||
class="w-64 rounded border px-3 py-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="dateFrom"
|
||||
type="date"
|
||||
class="rounded border px-3 py-2"
|
||||
/>
|
||||
<span class="text-sm text-gray-500">-</span>
|
||||
<input
|
||||
v-model="dateTo"
|
||||
type="date"
|
||||
class="rounded border px-3 py-2"
|
||||
/>
|
||||
<ButtonAction
|
||||
preset="custom"
|
||||
title="Filter List Laporan Tindakan"
|
||||
label="Filter"
|
||||
icon="i-lucide-filter"
|
||||
@click="
|
||||
() => {
|
||||
isDialogOpen = true
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<ButtonAction
|
||||
preset="custom"
|
||||
title="Riwayat Laporan Tindakan"
|
||||
icon="i-lucide-history"
|
||||
label="Riwayat Laporan Tindakan"
|
||||
@click="
|
||||
() => {
|
||||
isDialogOpen = true
|
||||
}
|
||||
"
|
||||
/>
|
||||
<ButtonAction
|
||||
preset="add"
|
||||
title="Tambah Data Laporan Tindakan"
|
||||
icon="i-lucide-plus"
|
||||
label="Tambah Data"
|
||||
@click="
|
||||
() => {
|
||||
goToEntry()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto p-4">
|
||||
<AppActionReportList
|
||||
:data="filtered"
|
||||
:pagination-meta="{
|
||||
recordCount: 2,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalPage: 1,
|
||||
hasPrev: false,
|
||||
hasNext: false,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog
|
||||
v-model:open="isDialogOpen"
|
||||
title="Arsip Riwayat Laporan Tindakan"
|
||||
size="2xl"
|
||||
prevent-outside
|
||||
@update:open="
|
||||
(value: any) => {
|
||||
isDialogOpen = value
|
||||
}
|
||||
"
|
||||
>
|
||||
<AppActionReportListHistory
|
||||
:data="filtered"
|
||||
:pagination-meta="{
|
||||
recordCount: 2,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalPage: 1,
|
||||
hasPrev: false,
|
||||
hasNext: false,
|
||||
}"
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
<RecordConfirmation
|
||||
v-model:open="isRecordConfirmationOpen"
|
||||
action="delete"
|
||||
:record="recItem"
|
||||
@confirm="
|
||||
() =>
|
||||
handleActionRemove(
|
||||
recItem.id,
|
||||
() => {
|
||||
router.go(0)
|
||||
},
|
||||
toast,
|
||||
)
|
||||
"
|
||||
@cancel=""
|
||||
>
|
||||
<template #default="{ record }">
|
||||
{{ console.log(JSON.stringify(record)) }}
|
||||
<div class="space-y-1 text-sm">
|
||||
<p
|
||||
v-for="field in config.delKeyNames"
|
||||
:key="field.key"
|
||||
:v-if="record?.[field.key]"
|
||||
>
|
||||
<span class="font-semibold">{{ field.label }}:</span>
|
||||
{{ record[field.key] }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</RecordConfirmation>
|
||||
</template>
|
||||
@@ -0,0 +1,68 @@
|
||||
export default {
|
||||
operatorTeam: {
|
||||
dpjpId: -1,
|
||||
operatorName: 'Julian Alvarez',
|
||||
assistantOperatorName: 'Arda Guller',
|
||||
instrumentNurseName: 'Kenan Yildiz',
|
||||
surgeryDate: '2025-11-13T14:29:00',
|
||||
actionDiagnosis: 'Sprei gratisnya mana',
|
||||
},
|
||||
procedures: [
|
||||
{
|
||||
id: -1,
|
||||
name: 'Ndase mumet',
|
||||
code: 'CX1',
|
||||
},
|
||||
],
|
||||
operationExecution: {
|
||||
surgeryType: 'khusus',
|
||||
billingCode: 'local',
|
||||
operationSystem: 'cito',
|
||||
surgeryCleanType: 'kotor',
|
||||
surgeryNumber: 'retry',
|
||||
birthPlaceNote: 'out3',
|
||||
personWeight: 100,
|
||||
operationDescription: 'asdsadsa1',
|
||||
birthRemark: 'lahir_hidup',
|
||||
|
||||
operationStartAt: '2025-11-13T14:29:00',
|
||||
operationEndAt: '2025-11-13T17:29:00',
|
||||
|
||||
anesthesiaStartAt: '2025-11-13T11:29:00',
|
||||
anesthesiaEndAt: '2025-11-13T18:29:00',
|
||||
},
|
||||
bloodInput: {
|
||||
type: 'tc',
|
||||
amount: {
|
||||
prc: null,
|
||||
wb: null,
|
||||
ffp: null,
|
||||
tc: 3243324,
|
||||
},
|
||||
},
|
||||
implant: {
|
||||
brand: 'Samsung',
|
||||
name: 'S.Komedi',
|
||||
companionName: 'When ya',
|
||||
},
|
||||
specimen: {
|
||||
destination: 'pa',
|
||||
},
|
||||
tissueNotes: [
|
||||
{
|
||||
note: 'Anjai',
|
||||
},
|
||||
{
|
||||
note: 'Ciee Kaget',
|
||||
},
|
||||
{
|
||||
note: 'Baper',
|
||||
},
|
||||
{
|
||||
note: 'Saltink weeh',
|
||||
},
|
||||
{
|
||||
note: 'Kaburrr',
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import mockData from './sample'
|
||||
|
||||
// types
|
||||
import { type ActionReportFormData } from '~/schemas/action-report.schema'
|
||||
import { type Encounter } from '~/models/encounter'
|
||||
|
||||
// Components
|
||||
import AppActionReportPreview from '~/components/app/action-report/preview.vue'
|
||||
import type { HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||
|
||||
// #region Props & Emits
|
||||
const router = useRouter()
|
||||
const { backToList, goToEntry } = useQueryCRUDMode('mode')
|
||||
const { recordId } = useQueryCRUDRecordId('record-id')
|
||||
|
||||
function onEditFromView() {
|
||||
goToEntry({ fromView: true })
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
encounter: Encounter
|
||||
}>()
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
const reportData = ref<ActionReportFormData | null>(null)
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
title: 'Detail Laporan Tindakan',
|
||||
icon: 'i-lucide-stethoscope',
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
onMounted(async () => {
|
||||
reportData.value = mockData as unknown as ActionReportFormData
|
||||
})
|
||||
// #endregion
|
||||
|
||||
// #region Functions
|
||||
// #endregion region
|
||||
|
||||
// #region Utilities & event handlers
|
||||
function onEdit() {
|
||||
router.push({
|
||||
name: 'action-report-id-edit',
|
||||
params: { id: 100 },
|
||||
})
|
||||
}
|
||||
function onBack() {}
|
||||
// #endregion
|
||||
|
||||
// #region Watchers
|
||||
// #endregion
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppActionReportPreview
|
||||
v-if="reportData"
|
||||
:data="reportData"
|
||||
@back="backToList"
|
||||
@edit="onEditFromView"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
import Nav from '~/components/pub/my-ui/nav-footer/ba-de-su.vue'
|
||||
import NavOk from '~/components/pub/my-ui/nav-footer/ok.vue'
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
|
||||
import { useQueryCRUDMode } from '~/composables/useQueryCRUD'
|
||||
import { type HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||
|
||||
// mcu src category
|
||||
import ScrCategorySwitcher from '~/components/app/mcu-src-category/switcher.vue'
|
||||
import { getList as getMcuCategoryList } from '~/services/mcu-src-category.service'
|
||||
|
||||
// mcu src
|
||||
import { type McuSrc } from '~/models/mcu-src'
|
||||
import { getList as getMcuSrcList } from '~/services/mcu-src.service'
|
||||
import McuSrcPicker from '~/components/app/mcu-src/picker-accordion.vue'
|
||||
|
||||
// mcu order
|
||||
import { getDetail } from '~/services/mcu-order.service'
|
||||
import Detail from '~/components/app/mcu-order/detail.vue'
|
||||
|
||||
// mcu order item, manually not using composable
|
||||
import {
|
||||
getList as getMcuOrderItemList,
|
||||
create as createMcuOrderItem,
|
||||
remove as removeMcuOrderItem,
|
||||
} from '~/services/mcu-order-item.service'
|
||||
import { type McuOrderItem } from '~/models/mcu-order-item'
|
||||
import Entry from '~/components/app/mcu-order/entry-for-ap.vue'
|
||||
|
||||
// props
|
||||
const props = defineProps<{
|
||||
encounter_id: number
|
||||
}>()
|
||||
|
||||
// declaration & flows
|
||||
|
||||
// MCU Order
|
||||
const { getQueryParam } = useQueryParam()
|
||||
const id = getQueryParam('id')
|
||||
const dataRes = await getDetail(
|
||||
typeof id === 'string' ? parseInt(id) : 0,
|
||||
{ includes: 'encounter,doctor,doctor-employee,doctor-employee-person' }
|
||||
)
|
||||
const data = dataRes.body?.data
|
||||
|
||||
// MCU Sources
|
||||
const { backToList } = useQueryCRUDMode()
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
title: 'Entry Order LAB PA',
|
||||
icon: 'i-lucide-box',
|
||||
}
|
||||
|
||||
function navClick(type: 'back' | 'delete' | 'draft' | 'submit') {
|
||||
if (type === 'back') {
|
||||
backToList()
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header
|
||||
:prep="headerPrep"
|
||||
:ref-search-nav="headerPrep.refSearchNav"
|
||||
class="mb-4 xl:mb-5"
|
||||
/>
|
||||
|
||||
<Entry />
|
||||
<Separator class="my-5" />
|
||||
|
||||
<div class="w-full flex justify-center">
|
||||
<Nav @click="navClick" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,159 @@
|
||||
<script setup lang="ts">
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
|
||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||
|
||||
import { ActionEvents, type HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||
|
||||
// Handlers
|
||||
import {
|
||||
recId,
|
||||
recAction,
|
||||
recItem,
|
||||
isReadonly,
|
||||
isFormEntryDialogOpen,
|
||||
isRecordConfirmationOpen,
|
||||
handleActionSave,
|
||||
handleActionRemove,
|
||||
} from '~/handlers/mcu-order.handler'
|
||||
|
||||
// Apps
|
||||
import { getList, getDetail } from '~/services/mcu-order.service'
|
||||
import List from '~/components/app/mcu-order/list.vue'
|
||||
import type { McuOrder } from '~/models/mcu-order'
|
||||
|
||||
const route = useRoute()
|
||||
const { setQueryParams } = useQueryParam()
|
||||
const { crudQueryParams } = useQueryCRUD()
|
||||
|
||||
const title = ref('')
|
||||
|
||||
const plainEid = route.params.id
|
||||
const encounter_id = (plainEid && typeof plainEid == 'string') ? parseInt(plainEid) : 0 // here the
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
paginationMeta,
|
||||
searchInput,
|
||||
handlePageChange,
|
||||
handleSearch,
|
||||
fetchData: getMyList,
|
||||
} = usePaginatedList<McuOrder>({
|
||||
fetchFn: async ({ page, search }) => {
|
||||
const result = await getList({
|
||||
search,
|
||||
page,
|
||||
'scope-code': "ap-lab",
|
||||
'encounter-id': encounter_id,
|
||||
includes: 'doctor,doctor-employee,doctor-employee-person',
|
||||
})
|
||||
return { success: result.success || false, body: result.body || {} }
|
||||
},
|
||||
entityName: 'mcu-order'
|
||||
})
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
title: 'Order Lab PA',
|
||||
icon: 'i-lucide-box',
|
||||
refSearchNav: {
|
||||
placeholder: 'Cari (min. 3 karakter)...',
|
||||
minLength: 3,
|
||||
debounceMs: 500,
|
||||
showValidationFeedback: true,
|
||||
onInput: (value: string) => {
|
||||
searchInput.value = value
|
||||
},
|
||||
onClick: () => {},
|
||||
onClear: () => {},
|
||||
},
|
||||
addNav: {
|
||||
label: 'Tambah',
|
||||
icon: 'i-lucide-plus',
|
||||
onClick: () => {
|
||||
recItem.value = null
|
||||
recId.value = 0
|
||||
crudQueryParams.value = { mode: 'entry', recordId: undefined }
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
|
||||
const getMyDetail = async (id: number | string) => {
|
||||
const result = await getDetail(id)
|
||||
if (result.success) {
|
||||
const currentValue = result.body?.data || {}
|
||||
recItem.value = currentValue
|
||||
isFormEntryDialogOpen.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for row actions when recId or recAction changes
|
||||
watch([recId, recAction], () => {
|
||||
switch (recAction.value) {
|
||||
case ActionEvents.showDetail:
|
||||
getMyDetail(recId.value)
|
||||
title.value = 'Detail Order Lab PK'
|
||||
isReadonly.value = true
|
||||
break
|
||||
case ActionEvents.showEdit:
|
||||
getMyDetail(recId.value)
|
||||
title.value = 'Edit Order Lab PK'
|
||||
isReadonly.value = false
|
||||
break
|
||||
case ActionEvents.showConfirmDelete:
|
||||
isRecordConfirmationOpen.value = true
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
watch([isFormEntryDialogOpen], async () => {
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
})
|
||||
|
||||
function cancel(data: McuOrder) {
|
||||
recId.value = data.id
|
||||
recItem.value = data
|
||||
isRecordConfirmationOpen.value = true
|
||||
}
|
||||
|
||||
function edit(data: McuOrder) {
|
||||
setQueryParams({
|
||||
'mode': 'entry',
|
||||
'id': data.id.toString()
|
||||
})
|
||||
recItem.value = data
|
||||
}
|
||||
|
||||
function submit(data: McuOrder) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header :prep="{ ...headerPrep }" />
|
||||
|
||||
<List
|
||||
v-if="!isLoading.dataListLoading"
|
||||
:data="data"
|
||||
:pagination-meta="paginationMeta"
|
||||
@cancel="cancel"
|
||||
@edit="edit"
|
||||
@submit="submit"
|
||||
/>
|
||||
|
||||
<RecordConfirmation
|
||||
v-model:open="isRecordConfirmationOpen"
|
||||
action="delete"
|
||||
:record="recItem"
|
||||
@confirm="() => handleActionRemove(recId, getMyList, toast)"
|
||||
@cancel=""
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
//
|
||||
import List from './list.vue'
|
||||
import Entry from './entry.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
encounter_id: number
|
||||
}>()
|
||||
|
||||
const { mode } = useQueryCRUDMode()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<List v-if="mode === 'list'" :encounter_id="encounter_id" />
|
||||
<Entry v-else :encounter_id="encounter_id" />
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import List from './list.vue'
|
||||
import Form from './form.vue'
|
||||
import View from './view.vue'
|
||||
|
||||
// Models
|
||||
import type { Encounter } from '~/models/encounter'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
encounter: Encounter
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { mode, goToEntry, goToView } = useQueryCRUDMode('mode')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<List
|
||||
v-if="mode === 'list'"
|
||||
:encounter="props.encounter"
|
||||
@add="goToEntry"
|
||||
@edit="goToEntry({ fromView: false })"
|
||||
@view="goToView"
|
||||
/>
|
||||
<View
|
||||
v-else-if="mode === 'view'"
|
||||
:encounter="props.encounter"
|
||||
/>
|
||||
<Form v-else />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import mockData from './sample'
|
||||
|
||||
// type
|
||||
import { genDoctor, type Doctor } from '~/models/doctor'
|
||||
import type { ActionReportFormData } from '~/schemas/action-report.schema'
|
||||
|
||||
// components
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
import AppAssessmentEducationEntry from '~/components/app/assessment-education/entry.vue'
|
||||
|
||||
// states
|
||||
const route = useRoute()
|
||||
const { mode, goBack } = useQueryCRUDMode('mode')
|
||||
const { recordId } = useQueryCRUDRecordId('record-id')
|
||||
const reportData = ref<ActionReportFormData>({} as unknown as ActionReportFormData)
|
||||
const doctors = ref<Doctor[]>([])
|
||||
const isLoading = ref<boolean>(false)
|
||||
|
||||
// TODO: dummy data
|
||||
;(() => {
|
||||
doctors.value = [genDoctor()]
|
||||
})()
|
||||
|
||||
const entryMode = ref<'add' | 'edit' | 'view'>('add')
|
||||
const isDataReady = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
if (mode.value === 'entry' && recordId.value) {
|
||||
entryMode.value = 'edit'
|
||||
await loadEntryForEdit(+recordId.value)
|
||||
} else {
|
||||
// Untuk mode 'add', langsung set ready
|
||||
isDataReady.value = true
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: map data
|
||||
async function loadEntryForEdit(id: number | string) {
|
||||
isLoading.value = true
|
||||
const result = mockData
|
||||
reportData.value = result as ActionReportFormData
|
||||
isLoading.value = false
|
||||
isDataReady.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppAssessmentEducationEntry
|
||||
v-if="isDataReady"
|
||||
:isLoading="isLoading"
|
||||
:mode="entryMode"
|
||||
@submit="(val: {}) => console.log(val)"
|
||||
@back="goBack"
|
||||
@error="
|
||||
(err: Error) => {
|
||||
toast({
|
||||
title: 'Terjadi Kesalahan',
|
||||
description: err.message,
|
||||
variant: 'destructive',
|
||||
})
|
||||
}
|
||||
"
|
||||
:doctors="doctors"
|
||||
:initialValues="reportData"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="flex items-center justify-center p-8"
|
||||
>
|
||||
<p class="text-muted-foreground">Memuat data...</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,276 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import AppAssessmentEducationList from '~/components/app/assessment-education/list.vue'
|
||||
import AppAssessmentEducationListHistory from '~/components/app/assessment-education/list-history.vue'
|
||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||
import { ButtonAction } from '~/components/pub/my-ui/form'
|
||||
|
||||
// config
|
||||
import { config } from '~/components/app/assessment-education/list.cfg'
|
||||
|
||||
// types
|
||||
import { ActionEvents } from '~/components/pub/my-ui/data/types'
|
||||
import type { Encounter } from '~/models/encounter'
|
||||
|
||||
// Samples
|
||||
import { sampleRows, type AssessmentEducationData } from '~/components/app/assessment-education/sample'
|
||||
import sampleReport from './sample'
|
||||
|
||||
// helpers
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
encounter: Encounter
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const emits = defineEmits<{
|
||||
(e: 'add'): void
|
||||
(e: 'edit', id: number | string): void
|
||||
(e: 'view', id: number | string): void
|
||||
}>()
|
||||
|
||||
// states
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { goToEntry, backToList } = useQueryCRUDMode('mode')
|
||||
const title = ref('')
|
||||
const search = ref('')
|
||||
const dateFrom = ref('')
|
||||
const dateTo = ref('')
|
||||
const isDialogOpen = ref<boolean>(false)
|
||||
const isLoading = ref<boolean>(false)
|
||||
|
||||
// #region mock
|
||||
// Handlers
|
||||
import {
|
||||
recId,
|
||||
recAction,
|
||||
recItem,
|
||||
isReadonly,
|
||||
isProcessing,
|
||||
isFormEntryDialogOpen,
|
||||
isRecordConfirmationOpen,
|
||||
onResetState,
|
||||
handleActionSave,
|
||||
handleActionEdit,
|
||||
handleActionRemove,
|
||||
handleCancelForm,
|
||||
} from '~/handlers/consultation.handler'
|
||||
// #endregion
|
||||
|
||||
// filter + pencarian sederhana (client-side)
|
||||
const filtered = computed(() => {
|
||||
const q = search.value.trim().toLowerCase()
|
||||
return sampleRows.filter((r: ActionReportData) => {
|
||||
if (q) {
|
||||
return r.nama.toLowerCase().includes(q) || r.noRm.toLowerCase().includes(q) || r.dokter.toLowerCase().includes(q)
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
const goEdit = (id: number | string) => {
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
...route.query,
|
||||
mode: 'entry',
|
||||
'record-id': id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const goView = (id: number | string) => {
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
...route.query,
|
||||
mode: 'view',
|
||||
'record-id': id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
|
||||
async function onGetDetail(id: number | string) {
|
||||
isLoading.value = true
|
||||
const res = sampleReport
|
||||
recItem.value = res
|
||||
console.log(res)
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
// #region watcher
|
||||
watch([recId, recAction], (newVal) => {
|
||||
const [id, action] = newVal
|
||||
|
||||
// Guard: jangan proses jika id = 0 atau action kosong
|
||||
if (!id || !action) return
|
||||
|
||||
switch (action) {
|
||||
case ActionEvents.showDetail:
|
||||
// onGetDetail(recId.value)
|
||||
goView(id)
|
||||
title.value = 'Detail Konsultasi'
|
||||
break
|
||||
case ActionEvents.showEdit:
|
||||
goEdit(id)
|
||||
title.value = 'Edit Konsultasi'
|
||||
break
|
||||
case ActionEvents.showConfirmDelete:
|
||||
isRecordConfirmationOpen.value = true
|
||||
break
|
||||
}
|
||||
|
||||
// Reset KEDUANYA menggunakan nextTick agar tidak trigger watcher lagi
|
||||
nextTick(() => {
|
||||
recId.value = 0
|
||||
recAction.value = ''
|
||||
})
|
||||
})
|
||||
// #endregion
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto max-w-full">
|
||||
<div class="border-b p-6">
|
||||
<h1 class="text-2xl font-semibold">Asesmen Kebutuhan Edukasi</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manajemen asesmen kebutuhan edukasi pasien rawat jalan</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3 border-b p-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="search"
|
||||
placeholder="Cari Nama / No.RM"
|
||||
class="w-64 rounded border px-3 py-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="dateFrom"
|
||||
type="date"
|
||||
class="rounded border px-3 py-2"
|
||||
/>
|
||||
<span class="text-sm text-gray-500">-</span>
|
||||
<input
|
||||
v-model="dateTo"
|
||||
type="date"
|
||||
class="rounded border px-3 py-2"
|
||||
/>
|
||||
<ButtonAction
|
||||
preset="custom"
|
||||
title="Filter List Asesmen"
|
||||
label="Filter"
|
||||
icon="i-lucide-filter"
|
||||
@click="
|
||||
() => {
|
||||
isDialogOpen = true
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<ButtonAction
|
||||
preset="custom"
|
||||
title="Riwayat"
|
||||
icon="i-lucide-history"
|
||||
label="Riwayat"
|
||||
@click="
|
||||
() => {
|
||||
isDialogOpen = true
|
||||
}
|
||||
"
|
||||
/>
|
||||
<ButtonAction
|
||||
preset="add"
|
||||
title="Tambah Asesmen"
|
||||
icon="i-lucide-plus"
|
||||
label="Asesmen"
|
||||
@click="
|
||||
() => {
|
||||
goToEntry()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto p-4">
|
||||
<AppAssessmentEducationList
|
||||
:data="filtered"
|
||||
:pagination-meta="{
|
||||
recordCount: 2,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalPage: 1,
|
||||
hasPrev: false,
|
||||
hasNext: false,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog
|
||||
v-model:open="isDialogOpen"
|
||||
title="Arsip Riwayat Asesmen Kebutuhan Edukasi"
|
||||
size="2xl"
|
||||
prevent-outside
|
||||
@update:open="
|
||||
(value: any) => {
|
||||
isDialogOpen = value
|
||||
}
|
||||
"
|
||||
>
|
||||
<AppAssessmentEducationListHistory
|
||||
:data="filtered"
|
||||
:pagination-meta="{
|
||||
recordCount: 2,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalPage: 1,
|
||||
hasPrev: false,
|
||||
hasNext: false,
|
||||
}"
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
<RecordConfirmation
|
||||
v-model:open="isRecordConfirmationOpen"
|
||||
action="delete"
|
||||
:record="recItem"
|
||||
@confirm="
|
||||
() =>
|
||||
handleActionRemove(
|
||||
recItem.id,
|
||||
() => {
|
||||
router.go(0)
|
||||
},
|
||||
toast,
|
||||
)
|
||||
"
|
||||
@cancel=""
|
||||
>
|
||||
<template #default="{ record }">
|
||||
{{ console.log(JSON.stringify(record)) }}
|
||||
<div class="space-y-1 text-sm">
|
||||
<p
|
||||
v-for="field in config.delKeyNames"
|
||||
:key="field.key"
|
||||
:v-if="record?.[field.key]"
|
||||
>
|
||||
<span class="font-semibold">{{ field.label }}:</span>
|
||||
{{ record[field.key] }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</RecordConfirmation>
|
||||
</template>
|
||||
@@ -0,0 +1,68 @@
|
||||
export default {
|
||||
operatorTeam: {
|
||||
dpjpId: -1,
|
||||
operatorName: 'Julian Alvarez',
|
||||
assistantOperatorName: 'Arda Guller',
|
||||
instrumentNurseName: 'Kenan Yildiz',
|
||||
surgeryDate: '2025-11-13T14:29:00',
|
||||
actionDiagnosis: 'Sprei gratisnya mana',
|
||||
},
|
||||
procedures: [
|
||||
{
|
||||
id: -1,
|
||||
name: 'Ndase mumet',
|
||||
code: 'CX1',
|
||||
},
|
||||
],
|
||||
operationExecution: {
|
||||
surgeryType: 'khusus',
|
||||
billingCode: 'local',
|
||||
operationSystem: 'cito',
|
||||
surgeryCleanType: 'kotor',
|
||||
surgeryNumber: 'retry',
|
||||
birthPlaceNote: 'out3',
|
||||
personWeight: 100,
|
||||
operationDescription: 'asdsadsa1',
|
||||
birthRemark: 'lahir_hidup',
|
||||
|
||||
operationStartAt: '2025-11-13T14:29:00',
|
||||
operationEndAt: '2025-11-13T17:29:00',
|
||||
|
||||
anesthesiaStartAt: '2025-11-13T11:29:00',
|
||||
anesthesiaEndAt: '2025-11-13T18:29:00',
|
||||
},
|
||||
bloodInput: {
|
||||
type: 'tc',
|
||||
amount: {
|
||||
prc: null,
|
||||
wb: null,
|
||||
ffp: null,
|
||||
tc: 3243324,
|
||||
},
|
||||
},
|
||||
implant: {
|
||||
brand: 'Samsung',
|
||||
name: 'S.Komedi',
|
||||
companionName: 'When ya',
|
||||
},
|
||||
specimen: {
|
||||
destination: 'pa',
|
||||
},
|
||||
tissueNotes: [
|
||||
{
|
||||
note: 'Anjai',
|
||||
},
|
||||
{
|
||||
note: 'Ciee Kaget',
|
||||
},
|
||||
{
|
||||
note: 'Baper',
|
||||
},
|
||||
{
|
||||
note: 'Saltink weeh',
|
||||
},
|
||||
{
|
||||
note: 'Kaburrr',
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import mockData from './sample'
|
||||
|
||||
// types
|
||||
import { type ActionReportFormData } from '~/schemas/action-report.schema'
|
||||
import { type Encounter } from '~/models/encounter'
|
||||
|
||||
// Components
|
||||
import AppActionReportPreview from '~/components/app/action-report/preview.vue'
|
||||
import type { HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||
|
||||
// #region Props & Emits
|
||||
const router = useRouter()
|
||||
const { backToList, goToEntry } = useQueryCRUDMode('mode')
|
||||
const { recordId } = useQueryCRUDRecordId('record-id')
|
||||
|
||||
function onEditFromView() {
|
||||
goToEntry({ fromView: true })
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
encounter: Encounter
|
||||
}>()
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
const reportData = ref<ActionReportFormData | null>(null)
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
title: 'Detail Laporan Tindakan',
|
||||
icon: 'i-lucide-stethoscope',
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
onMounted(async () => {
|
||||
reportData.value = mockData as unknown as ActionReportFormData
|
||||
})
|
||||
// #endregion
|
||||
|
||||
// #region Functions
|
||||
// #endregion region
|
||||
|
||||
// #region Utilities & event handlers
|
||||
function onEdit() {
|
||||
router.push({
|
||||
name: 'action-report-id-edit',
|
||||
params: { id: 100 },
|
||||
})
|
||||
}
|
||||
function onBack() {}
|
||||
// #endregion
|
||||
|
||||
// #region Watchers
|
||||
// #endregion
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppActionReportPreview
|
||||
v-if="reportData"
|
||||
:data="reportData"
|
||||
@back="backToList"
|
||||
@edit="onEditFromView"
|
||||
/>
|
||||
</template>
|
||||
@@ -10,13 +10,14 @@ import Confirmation from '~/components/pub/my-ui/confirmation/confirmation.vue'
|
||||
import { type ControlLetter } from '~/models/control-letter'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
callbackUrl?: string
|
||||
}>()
|
||||
}>(), {
|
||||
|
||||
})
|
||||
|
||||
// form related state
|
||||
const route = useRoute()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const controlLetterForm = ref<ExposedForm<any> | null>(null)
|
||||
// #endregion
|
||||
|
||||
@@ -72,7 +73,7 @@ async function composeFormData(): Promise<ControlLetter> {
|
||||
if (!allValid) return Promise.reject('Form validation failed')
|
||||
|
||||
const formData = controlLetter?.values
|
||||
formData.encounter_id = encounterId
|
||||
formData.encounter_id = props.encounter_id
|
||||
return new Promise((resolve) => resolve(formData))
|
||||
}
|
||||
// #endregion region
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { withBase } from '~/models/_base'
|
||||
import type { HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||
import type { Patient } from '~/models/patient'
|
||||
import type { Person } from '~/models/person'
|
||||
import { getDetail } from '~/services/control-letter.service'
|
||||
|
||||
// Components
|
||||
@@ -11,18 +8,19 @@ import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import type { ControlLetter } from '~/models/control-letter'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
}>()
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
record_id: number
|
||||
redirectToForm?: (myRecord_id?: any) => void
|
||||
}>(), {
|
||||
redirectToForm: () => {},
|
||||
})
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const controlLetterId = typeof route.params.control_letter_id == 'string' ? parseInt(route.params.control_letter_id) : 0
|
||||
|
||||
const controlLetter = ref<ControlLetter | null>(null)
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
@@ -34,7 +32,7 @@ const headerPrep: HeaderPrep = {
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
onMounted(async () => {
|
||||
const result = await getDetail(controlLetterId, {
|
||||
const result = await getDetail(props.record_id, {
|
||||
includes: "unit,specialist,subspecialist,doctor-employee-person",
|
||||
})
|
||||
if (result.success) {
|
||||
@@ -54,11 +52,7 @@ function goBack() {
|
||||
function handleAction(type: string) {
|
||||
switch (type) {
|
||||
case 'edit':
|
||||
// TODO: Handle edit action
|
||||
navigateTo({
|
||||
name: 'rehab-encounter-id-control-letter-control_letter_id-edit',
|
||||
params: { id: encounterId, "control_letter_id": controlLetterId },
|
||||
})
|
||||
props.redirectToForm(props.record_id)
|
||||
break
|
||||
|
||||
case 'back':
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { Patient, genPatientProps } from '~/models/patient'
|
||||
import type { ExposedForm } from '~/types/form'
|
||||
import type { PatientBase } from '~/models/patient'
|
||||
import Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
||||
import { genPatient } from '~/models/patient'
|
||||
import { PatientSchema } from '~/schemas/patient.schema'
|
||||
import { PersonAddressRelativeSchema } from '~/schemas/person-address-relative.schema'
|
||||
import { PersonAddressSchema } from '~/schemas/person-address.schema'
|
||||
import { PersonContactListSchema } from '~/schemas/person-contact.schema'
|
||||
import { PersonFamiliesSchema } from '~/schemas/person-family.schema'
|
||||
import { ResponsiblePersonSchema } from '~/schemas/person-relative.schema'
|
||||
import { uploadAttachment } from '~/services/patient.service'
|
||||
import { getDetail, update } from '~/services/control-letter.service'
|
||||
|
||||
import {
|
||||
@@ -26,27 +16,26 @@ import {
|
||||
} from '~/handlers/control-letter.handler'
|
||||
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
import { withBase } from '~/models/_base'
|
||||
import type { Person } from '~/models/person'
|
||||
import Confirmation from '~/components/pub/my-ui/confirmation/confirmation.vue'
|
||||
import type { ControlLetter } from '~/models/control-letter'
|
||||
import { ControlLetterSchema } from '~/schemas/control-letter.schema'
|
||||
import { formatDateYyyyMmDd } from '~/lib/date'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
callbackUrl?: string
|
||||
}>()
|
||||
record_id: number
|
||||
}>(), {
|
||||
|
||||
})
|
||||
|
||||
// form related state
|
||||
const controlLetterForm = ref<ExposedForm<any> | null>(null)
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const controlLetterId = typeof route.params.control_letter_id == 'string' ? parseInt(route.params.control_letter_id) : 0
|
||||
|
||||
const isConfirmationOpen = ref(false)
|
||||
const controlLetter = ref({})
|
||||
@@ -58,7 +47,7 @@ const selectedSubSpecialistId = ref<number|null>(null)
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
onMounted(async () => {
|
||||
const result = await getDetail(controlLetterId)
|
||||
const result = await getDetail(props.record_id)
|
||||
if (result.success) {
|
||||
const responseData = {...result.body.data, date: formatDateYyyyMmDd(result.body.data.date)}
|
||||
selectedUnitId.value = responseData?.unit_code
|
||||
@@ -78,7 +67,7 @@ function goBack() {
|
||||
|
||||
async function handleConfirmAdd() {
|
||||
const response = await handleActionEdit(
|
||||
controlLetterId,
|
||||
props.record_id,
|
||||
await composeFormData(),
|
||||
() => { },
|
||||
() => { },
|
||||
@@ -99,7 +88,7 @@ async function composeFormData(): Promise<ControlLetter> {
|
||||
if (!allValid) return Promise.reject('Form validation failed')
|
||||
|
||||
const formData = controlLetter?.values
|
||||
formData.encounter_id = encounterId
|
||||
formData.encounter_id = props.encounter_id
|
||||
return new Promise((resolve) => resolve(formData))
|
||||
}
|
||||
// #endregion region
|
||||
|
||||
@@ -13,24 +13,28 @@ import type { Encounter } from '~/models/encounter'
|
||||
import WarningAlert from '~/components/pub/my-ui/alert/warning-alert.vue'
|
||||
// import type { PagePermission } from '~/models/role'
|
||||
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
|
||||
import { permissions } from '~/const/page-permission/chemoteraphy'
|
||||
import { permissions } from '~/const/page-permission/ambulatory'
|
||||
import { unauthorizedToast } from '~/lib/utils'
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import DocPreviewDialog from '~/components/pub/my-ui/modal/doc-preview-dialog.vue'
|
||||
import HistoryDialog from '~/components/app/control-letter/history-dialog.vue'
|
||||
import type { RoleAccesses } from '~/models/role'
|
||||
// #endregion
|
||||
|
||||
// #region Permission
|
||||
const roleAccess = permissions['/rehab/encounter'] || {}
|
||||
const roleAccess: RoleAccesses = permissions['/ambulatory/encounter'] || {}
|
||||
const { getPagePermissions } = useRBAC()
|
||||
const pagePermission = getPagePermissions(roleAccess)
|
||||
|
||||
// #region State
|
||||
const props = defineProps<{
|
||||
encounter?: Encounter
|
||||
}>()
|
||||
const route = useRoute()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
redirectToForm?: (myRecord_id?: any) => void
|
||||
redirectToDetail?: (myRecord_id: string|number) => void
|
||||
}>(), {
|
||||
redirectToForm: () => { },
|
||||
redirectToDetail: () => { }
|
||||
})
|
||||
|
||||
const { data, isLoading, paginationMeta, searchInput, handlePageChange, handleSearch, fetchData } = usePaginatedList({
|
||||
fetchFn: (params) => getList({ ...params, includes: 'specialist,subspecialist,doctor-employee-person', }),
|
||||
@@ -73,10 +77,11 @@ const headerPrep: HeaderPrep = {
|
||||
if (pagePermission.canCreate) {
|
||||
headerPrep.addNav = {
|
||||
label: "Surat Kontrol",
|
||||
onClick: () => navigateTo({
|
||||
name: 'rehab-encounter-id-control-letter-add',
|
||||
params: { id: encounterId },
|
||||
}),
|
||||
onClick: () => {
|
||||
console.log('redirectToForm')
|
||||
console.log(props.redirectToForm())
|
||||
props.redirectToForm()
|
||||
},
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
@@ -136,18 +141,12 @@ provide('table_data_loader', isLoading)
|
||||
watch([recId, recAction, timestamp], () => {
|
||||
switch (recAction.value) {
|
||||
case ActionEvents.showDetail:
|
||||
navigateTo({
|
||||
name: 'rehab-encounter-id-control-letter-control_letter_id',
|
||||
params: { id: encounterId, "control_letter_id": recId.value },
|
||||
})
|
||||
props.redirectToDetail(recId.value)
|
||||
break
|
||||
|
||||
case ActionEvents.showEdit:
|
||||
if(pagePermission.canUpdate){
|
||||
navigateTo({
|
||||
name: 'rehab-encounter-id-control-letter-control_letter_id-edit',
|
||||
params: { id: encounterId, "control_letter_id": recId.value },
|
||||
})
|
||||
props.redirectToForm(recId.value)
|
||||
} else {
|
||||
unauthorizedToast()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { crudQueryParamsMode } from '~/lib/system-constants';
|
||||
import List from './list.vue'
|
||||
import Detail from './detail.vue'
|
||||
import Add from './add.vue'
|
||||
import Edit from './edit.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
encounter_id: number
|
||||
}>()
|
||||
|
||||
const { crudQueryParams, goToDetail, goToEntry } = useQueryCRUD()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<List v-if="crudQueryParams.mode === crudQueryParamsMode.list && !crudQueryParams.recordId"
|
||||
:encounter_id="encounter_id"
|
||||
:redirectToForm="goToEntry"
|
||||
:redirectToDetail="goToDetail"/>
|
||||
<Detail v-else-if="crudQueryParams.mode === crudQueryParamsMode.list && crudQueryParams.recordId"
|
||||
:encounter_id="encounter_id"
|
||||
:record_id="crudQueryParams.recordId"
|
||||
:redirectToForm="goToEntry"/>
|
||||
<Add v-else-if="crudQueryParams.mode === crudQueryParamsMode.entry && !crudQueryParams.recordId"
|
||||
:encounter_id="encounter_id" />
|
||||
<Edit v-else-if="crudQueryParams.mode === crudQueryParamsMode.entry && crudQueryParams.recordId"
|
||||
:encounter_id="encounter_id"
|
||||
:record_id="crudQueryParams.recordId" />
|
||||
|
||||
<List v-else :encounter_id="encounter_id" :redirectToForm="goToEntry" :redirectToDetail="goToDetail" />
|
||||
</template>
|
||||
@@ -10,13 +10,13 @@ import { toFormData } from '~/lib/utils'
|
||||
import { uploadAttachment } from '~/services/supporting-document.service'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
callbackUrl?: string
|
||||
}>()
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
}>(), {
|
||||
|
||||
})
|
||||
|
||||
// form related state
|
||||
const route = useRoute()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const inputForm = ref<ExposedForm<any> | null>(null)
|
||||
const { user } = useUserStore()
|
||||
// #endregion
|
||||
@@ -43,20 +43,14 @@ async function handleConfirmAdd() {
|
||||
const inputFormData: FormData = toFormData(inputData)
|
||||
|
||||
const response = await handleActionSave(inputFormData, () => { }, () => { }, toast, )
|
||||
const data = (response?.body?.data ?? null)
|
||||
if (!data) return
|
||||
|
||||
// // If has callback provided redirect to callback with patientData
|
||||
if (props.callbackUrl) {
|
||||
navigateTo(props.callbackUrl + '?control-letter-id=' + inputData.id)
|
||||
}
|
||||
// const data = (response?.body?.data ?? null)
|
||||
goBack()
|
||||
}
|
||||
|
||||
async function composeFormData(): Promise<any> {
|
||||
inputForm.value?.setValues({
|
||||
...inputForm.value?.values,
|
||||
ref_id: encounterId,
|
||||
ref_id: props.encounter_id,
|
||||
upload_employee_id: user.employee_id
|
||||
})
|
||||
|
||||
@@ -83,13 +77,7 @@ async function handleActionClick(eventType: string) {
|
||||
isConfirmationOpen.value = true
|
||||
}
|
||||
|
||||
if (eventType === 'back') {
|
||||
if (props.callbackUrl) {
|
||||
await navigateTo(props.callbackUrl)
|
||||
return
|
||||
}
|
||||
goBack()
|
||||
}
|
||||
if (eventType === 'back') goBack()
|
||||
}
|
||||
|
||||
function handleCancelAdd() {
|
||||
|
||||
@@ -8,15 +8,15 @@ import Confirmation from '~/components/pub/my-ui/confirmation/confirmation.vue'
|
||||
import { DocumentUploadSchema } from '~/schemas/document-upload.schema'
|
||||
import { getDetail } from '~/services/supporting-document.service'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
callbackUrl?: string
|
||||
}>()
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
record_id: number
|
||||
}>(), {
|
||||
|
||||
})
|
||||
|
||||
// form related state
|
||||
const route = useRoute()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const docId = typeof route.params.document_id == 'string' ? parseInt(route.params.document_id) : 0
|
||||
const docId = props.record_id
|
||||
const inputForm = ref<ExposedForm<any> | null>(null)
|
||||
// #endregion
|
||||
|
||||
@@ -77,7 +77,7 @@ async function composeFormData(): Promise<any> {
|
||||
if (!allValid) return Promise.reject('Form validation failed')
|
||||
|
||||
const formData = inputFormState?.values
|
||||
formData.encounter_id = encounterId
|
||||
formData.encounter_id = props.encounter_id
|
||||
return new Promise((resolve) => resolve(formData))
|
||||
}
|
||||
// #endregion region
|
||||
@@ -88,14 +88,7 @@ async function handleActionClick(eventType: string) {
|
||||
isConfirmationOpen.value = true
|
||||
}
|
||||
|
||||
if (eventType === 'back') {
|
||||
if (props.callbackUrl) {
|
||||
await navigateTo(props.callbackUrl)
|
||||
return
|
||||
}
|
||||
|
||||
goBack()
|
||||
}
|
||||
if (eventType === 'back') goBack()
|
||||
}
|
||||
|
||||
function handleCancelAdd() {
|
||||
@@ -109,7 +102,7 @@ function handleCancelAdd() {
|
||||
|
||||
<template>
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg font-semibold xl:text-xl">
|
||||
<h1>Upload Dokumen</h1>
|
||||
<h1>Update Upload Dokumen</h1>
|
||||
</div>
|
||||
<AppDocumentUploadEntryForm
|
||||
ref="inputForm"
|
||||
|
||||
@@ -9,33 +9,30 @@ import type { Encounter } from '~/models/encounter'
|
||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||
import DocPreviewDialog from '~/components/pub/my-ui/modal/doc-preview-dialog.vue'
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import type { PagePermission } from '~/models/role'
|
||||
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
|
||||
import type { Permission, RoleAccesses, } from '~/models/role'
|
||||
import { unauthorizedToast } from '~/lib/utils'
|
||||
import { permissions } from '~/const/page-permission/ambulatory'
|
||||
import { usePageChecker } from '~/lib/page-checker'
|
||||
// #endregion
|
||||
|
||||
|
||||
// #region Permission
|
||||
const roleAccess: PagePermission = PAGE_PERMISSIONS['/rehab/encounter']
|
||||
const roleAccess: RoleAccesses = permissions['/ambulatory/encounter'] || {}
|
||||
const { getPagePermissions } = useRBAC()
|
||||
const pagePermission = getPagePermissions(roleAccess)
|
||||
|
||||
// const {user,userRole} = useUserStore()
|
||||
// const {getUserPermissions} = useRBAC()
|
||||
// #endregion
|
||||
|
||||
// #region State
|
||||
const props = defineProps<{
|
||||
encounter?: Encounter
|
||||
refresh: () => void
|
||||
}>()
|
||||
|
||||
const route = useRoute()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
redirectToForm?: (myRecord_id?: any) => void
|
||||
}>(), {
|
||||
redirectToForm: () => { }
|
||||
})
|
||||
|
||||
const { data, paginationMeta, handlePageChange, handleSearch, searchInput, fetchData } = usePaginatedList({
|
||||
fetchFn: (params) => getList({
|
||||
'encounter-id': encounterId,
|
||||
'encounter-id': props.encounter_id,
|
||||
// includes: "employee",
|
||||
...params,
|
||||
}),
|
||||
@@ -56,10 +53,9 @@ const headerPrep: HeaderPrep = {
|
||||
if (pagePermission.canCreate) {
|
||||
headerPrep.addNav = {
|
||||
label: "Upload Dokumen",
|
||||
onClick: () => navigateTo({
|
||||
name: 'rehab-encounter-id-document-upload-add',
|
||||
params: { id: encounterId },
|
||||
}),
|
||||
onClick: () => {
|
||||
props.redirectToForm()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +74,7 @@ const refSearchNav: RefSearchNav = {
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
onMounted(() => {
|
||||
|
||||
})
|
||||
// #endregion
|
||||
|
||||
@@ -99,7 +96,6 @@ async function handleConfirmDelete(record: any, action: string) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleCancelConfirmation() {
|
||||
// Reset record state when cancelled
|
||||
recId.value = 0
|
||||
@@ -124,10 +120,7 @@ watch([recId, recAction, timestamp], () => {
|
||||
|
||||
case ActionEvents.showEdit:
|
||||
if(pagePermission.canUpdate){
|
||||
navigateTo({
|
||||
name: 'rehab-encounter-id-document-upload-document_id-edit',
|
||||
params: { id: encounterId, "document_id": recId.value },
|
||||
})
|
||||
props.redirectToForm(recId.value)
|
||||
} else {
|
||||
unauthorizedToast()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { crudQueryParamsMode } from '~/lib/system-constants';
|
||||
import List from './list.vue'
|
||||
import Add from './add.vue'
|
||||
import Edit from './edit.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
encounter_id: number
|
||||
}>()
|
||||
|
||||
const { crudQueryParams, goToEntry } = useQueryCRUD()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<List v-if="crudQueryParams.mode === crudQueryParamsMode.list" :encounter_id="encounter_id" :redirectToForm="goToEntry" />
|
||||
<Add v-else-if="crudQueryParams.mode === crudQueryParamsMode.entry && !crudQueryParams.recordId" :encounter_id="encounter_id" />
|
||||
<Edit v-else-if="crudQueryParams.mode === crudQueryParamsMode.entry && crudQueryParams.recordId"
|
||||
:encounter_id="encounter_id"
|
||||
:record_id="crudQueryParams.recordId" />
|
||||
|
||||
<List v-else :encounter_id="encounter_id" :redirectToForm="goToEntry" />
|
||||
</template>
|
||||
@@ -169,10 +169,10 @@ async function getPatientList() {
|
||||
try {
|
||||
const params: any = { includes: includesParams, ...filterParams.value }
|
||||
if (props.classCode) {
|
||||
params.class_code = props.classCode
|
||||
params['class-code'] = props.classCode
|
||||
}
|
||||
if (props.subClassCode) {
|
||||
params.sub_class_code = props.subClassCode
|
||||
params['sub-class-code'] = props.subClassCode
|
||||
}
|
||||
const result = await getEncounterList(params)
|
||||
if (result.success) {
|
||||
|
||||
@@ -29,8 +29,10 @@ import CpLabOrder from '~/components/content/cp-lab-order/main.vue'
|
||||
import Radiology from '~/components/content/radiology-order/main.vue'
|
||||
import Consultation from '~/components/content/consultation/list.vue'
|
||||
import Cprj from '~/components/content/cprj/entry.vue'
|
||||
import ActionReport from '~/components/content/action-report/entry.vue'
|
||||
import DocUploadList from '~/components/content/document-upload/list.vue'
|
||||
import GeneralConsentList from '~/components/content/general-consent/entry.vue'
|
||||
import SummaryMedic from '~/components/content/summary-medic/entry.vue'
|
||||
import ResumeList from '~/components/content/resume/list.vue'
|
||||
import ControlLetterList from '~/components/content/control-letter/list.vue'
|
||||
import InitialNursingStudy from '~/components/content/initial-nursing/entry.vue'
|
||||
@@ -56,7 +58,7 @@ const router = useRouter()
|
||||
const { user, userActiveRole, getActiveRole } = useUserStore()
|
||||
const activeRole = getActiveRole()
|
||||
const activePosition = ref(getServicePosition(activeRole))
|
||||
const menus = ref([] as any)
|
||||
const menus = shallowRef([] as any)
|
||||
const activeMenu = computed({
|
||||
get: () => (route.query?.menu && typeof route.query.menu === 'string' ? route.query.menu : 'status'),
|
||||
set: (value: string) => {
|
||||
@@ -101,6 +103,13 @@ const protocolRows = [
|
||||
{ value: 'therapy-protocol', label: 'Protokol Terapi' },
|
||||
{ value: 'education-assessment', label: 'Asesmen Kebutuhan Edukasi' },
|
||||
{ value: 'patient-note', label: 'CPRJ', component: Cprj, props: { encounter: data } },
|
||||
{ value: 'summary-medic', label: 'Profil Ringkasan Medis', component: SummaryMedic, props: { encounter: data } },
|
||||
{ value: 'consent', label: 'General Consent', component: GeneralConsentList, props: { encounter: data } },
|
||||
{ value: 'prescription', label: 'Order Obat', component: Prescription, props: { encounter_id: data.value.id } },
|
||||
{ value: 'device-order', label: 'Order Alkes', component: DeviceOrder, props: { encounter_id: data.value.id } },
|
||||
{ value: 'device', label: 'Order Alkes' },
|
||||
{ value: 'mcu-radiology', label: 'Order Radiologi', component: Radiology, props: { encounter_id: data.value.id } },
|
||||
{ value: 'mcu-lab-cp', label: 'Order Lab PK', component: CpLabOrder, props: { encounter_id: data.value.id } },
|
||||
{ value: 'consent', label: 'General Consent', component: GeneralConsentList, props: { encounter: data } },
|
||||
{
|
||||
value: 'initial-nursing-study',
|
||||
@@ -108,11 +117,6 @@ const protocolRows = [
|
||||
component: InitialNursingStudy,
|
||||
props: { encounter: data },
|
||||
},
|
||||
{ value: 'prescription', label: 'Order Obat', component: Prescription, props: { encounter_id: data.value.id } },
|
||||
{ value: 'device-order', label: 'Order Alkes', component: DeviceOrder, props: { encounter_id: data.value.id } },
|
||||
{ value: 'device', label: 'Order Alkes' },
|
||||
{ value: 'mcu-radiology', label: 'Order Radiologi', component: Radiology, props: { encounter_id: data.value.id } },
|
||||
{ value: 'mcu-lab-cp', label: 'Order Lab PK', component: CpLabOrder, props: { encounter_id: data.value.id } },
|
||||
{ value: 'mcu-lab-micro', label: 'Order Lab Mikro' },
|
||||
{ value: 'mcu-lab-pa', label: 'Order Lab PA' },
|
||||
{ value: 'medical-action', label: 'Order Ruang Tindakan' },
|
||||
@@ -121,6 +125,13 @@ const protocolRows = [
|
||||
{ value: 'resume', label: 'Resume', component: ResumeList, props: { encounter: data } },
|
||||
{ value: 'control', label: 'Surat Kontrol', component: ControlLetterList, props: { encounter: data } },
|
||||
{ value: 'screening', label: 'Skrinning MPP' },
|
||||
{
|
||||
value: 'report',
|
||||
label: 'Laporan Tindakan',
|
||||
groups: ['ambulatory', 'rehabilitation', 'chemotherapy'],
|
||||
component: ActionReport,
|
||||
props: { encounter: data },
|
||||
},
|
||||
{
|
||||
value: 'supporting-document',
|
||||
label: 'Upload Dokumen Pendukung',
|
||||
|
||||
@@ -117,7 +117,6 @@ async function actionHandler(type: string) {
|
||||
})
|
||||
}
|
||||
|
||||
console.log('data', result)
|
||||
const resp = await handleActionSave(
|
||||
{
|
||||
...payload.value,
|
||||
|
||||
@@ -10,17 +10,16 @@ import { getDetail } from '~/services/kfr.service';
|
||||
|
||||
// #region Props & Emits
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
callbackUrl?: string
|
||||
mode?: 'add' | 'edit'
|
||||
record_id: number
|
||||
}>(), {
|
||||
mode: "add",
|
||||
|
||||
})
|
||||
|
||||
// form related state
|
||||
const route = useRoute()
|
||||
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const kfrId = typeof route.params.kfr_id == 'string' ? parseInt(route.params.kfr_id) : 0
|
||||
const mode = computed(() => props.record_id ? `edit` : `add`)
|
||||
const inputForm = ref<ExposedForm<any> | null>(null)
|
||||
// #endregion
|
||||
|
||||
@@ -31,13 +30,13 @@ const isConfirmationOpen = ref(false)
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
onMounted(() => {
|
||||
if(props.mode === `edit`) init()
|
||||
if(mode.value === `edit`) init()
|
||||
})
|
||||
// #endregion
|
||||
|
||||
// #region Functions
|
||||
async function init(){
|
||||
const result = await getDetail(kfrId)
|
||||
const result = await getDetail(props.record_id)
|
||||
if (result.success) {
|
||||
const currentValue = result.body?.data || {}
|
||||
inputForm.value?.setValues(currentValue)
|
||||
@@ -50,7 +49,7 @@ function goBack() {
|
||||
|
||||
async function handleConfirmAdd() {
|
||||
const inputData: any = await composeFormData()
|
||||
const response = props.mode === `add`
|
||||
const response = mode.value === `add`
|
||||
? await handleActionSave(
|
||||
inputData,
|
||||
() => { },
|
||||
@@ -58,7 +57,7 @@ async function handleConfirmAdd() {
|
||||
toast,
|
||||
)
|
||||
: await handleActionEdit(
|
||||
kfrId,
|
||||
props.record_id,
|
||||
inputData,
|
||||
() => { },
|
||||
() => { },
|
||||
@@ -82,7 +81,7 @@ async function composeFormData(): Promise<any> {
|
||||
if (!allValid) return Promise.reject('Form validation failed')
|
||||
|
||||
const formData = input?.values
|
||||
formData.encounter_id = encounterId
|
||||
formData.encounter_id = props.encounter_id
|
||||
|
||||
return new Promise((resolve) => resolve(formData))
|
||||
}
|
||||
@@ -123,7 +122,7 @@ const initial = {
|
||||
|
||||
<template>
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg font-semibold xl:text-xl">
|
||||
{{ props.mode === "add" ? `Tambah` : `Update` }} Formulir Rawat Jalan KFR
|
||||
Formulir Rawat Jalan KFR
|
||||
</div>
|
||||
<AppKfrEntry
|
||||
ref="inputForm"
|
||||
|
||||
@@ -27,10 +27,12 @@ import Confirmation from '~/components/pub/my-ui/confirmation/confirmation.vue'
|
||||
interface Props {
|
||||
encounter: Encounter
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const route = useRoute()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const kfrId = typeof route.params.kfr_id == 'string' ? parseInt(route.params.kfr_id) : 0
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
redirectToForm?: (myRecord_id?: any) => void
|
||||
}>(), {
|
||||
redirectToForm: () => { }
|
||||
})
|
||||
// #endregion
|
||||
|
||||
// #region State
|
||||
@@ -104,17 +106,16 @@ const addBtnTxt = computed(() => {
|
||||
}
|
||||
return `Tambah Asesmen`
|
||||
})
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
const headerPrep: HeaderPrep = {
|
||||
title: "Formulir Rawat Jalan KFR",
|
||||
icon: 'i-lucide-newspaper',
|
||||
}
|
||||
if(isDoctor.value || isAdmin.value) {
|
||||
headerPrep.addNav = {
|
||||
label: addBtnTxt.value,
|
||||
onClick: () => navigateTo({
|
||||
name: 'rehab-encounter-id-kfr-add',
|
||||
}),
|
||||
onClick: () => {
|
||||
props.redirectToForm()
|
||||
},
|
||||
}
|
||||
}
|
||||
if(!isAssessment.value) {
|
||||
@@ -283,12 +284,7 @@ watch([recId, recAction, timestamp], () => {
|
||||
switch (recAction.value) {
|
||||
case ActionEvents.showEdit:
|
||||
// if(pagePermission.canUpdate) {
|
||||
navigateTo({
|
||||
name: 'rehab-encounter-id-kfr-kfr_id-edit',
|
||||
params: {
|
||||
kfr_id: kfrId
|
||||
}
|
||||
})
|
||||
props.redirectToForm(recId.value)
|
||||
// } else {
|
||||
// unauthorizedToast()
|
||||
// }
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { crudQueryParamsMode } from '~/lib/system-constants';
|
||||
import List from './list.vue'
|
||||
import Entry from './entry.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
encounter_id: number
|
||||
}>()
|
||||
|
||||
const { crudQueryParams, goToDetail, goToEntry } = useQueryCRUD()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<List v-if="crudQueryParams.mode === crudQueryParamsMode.list"
|
||||
:encounter_id="encounter_id"
|
||||
:redirectToForm="goToEntry"
|
||||
:redirectToDetail="goToDetail"/>
|
||||
<Entry v-else-if="crudQueryParams.mode === crudQueryParamsMode.entry"
|
||||
:encounter_id="encounter_id"
|
||||
:record_id="crudQueryParams.recordId" />
|
||||
|
||||
<List v-else :encounter_id="encounter_id" :redirectToForm="goToEntry" :redirectToDetail="goToDetail" />
|
||||
</template>
|
||||
@@ -131,6 +131,7 @@ async function getItems() {
|
||||
|
||||
<Detail :data="data" />
|
||||
|
||||
<div class="font-semibold pt-4 text-sm mb-1">Daftar Item</div>
|
||||
<ItemListEntry
|
||||
:data="items"
|
||||
@requestItem="requestItem"/>
|
||||
|
||||
@@ -11,17 +11,18 @@ import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import type { Prb } from '~/models/prb'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
}>()
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
record_id: number
|
||||
}>(), {
|
||||
|
||||
})
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const PrbId = typeof route.params.surgery_report_id == 'string' ? parseInt(route.params.surgery_report_id) : 0
|
||||
const Prb = ref<Prb | null>(null)
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
|
||||
@@ -4,21 +4,11 @@ import type { Patient, genPatientProps } from '~/models/patient'
|
||||
import type { ExposedForm } from '~/types/form'
|
||||
import type { PatientBase } from '~/models/patient'
|
||||
import Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import { genPatient } from '~/models/patient'
|
||||
import { PatientSchema } from '~/schemas/patient.schema'
|
||||
import { PersonAddressRelativeSchema } from '~/schemas/person-address-relative.schema'
|
||||
import { PersonAddressSchema } from '~/schemas/person-address.schema'
|
||||
import { PersonContactListSchema } from '~/schemas/person-contact.schema'
|
||||
import { PersonFamiliesSchema } from '~/schemas/person-family.schema'
|
||||
import { ResponsiblePersonSchema } from '~/schemas/person-relative.schema'
|
||||
import { uploadAttachment } from '~/services/patient.service'
|
||||
import { getDetail, update } from '~/services/prb.service'
|
||||
import type { Prb } from '~/models/prb'
|
||||
import ActionDialog from '~/components/pub/my-ui/nav-footer/ba-su.vue'
|
||||
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
import { withBase } from '~/models/_base'
|
||||
import Confirmation from '~/components/pub/my-ui/confirmation/confirmation.vue'
|
||||
import { PrbSchema } from '~/schemas/prb.schema'
|
||||
import { formatDateYyyyMmDd } from '~/lib/date'
|
||||
@@ -29,8 +19,10 @@ import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types
|
||||
import { formatDateToDatetimeLocal } from '~/lib/utils'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = withDefaults(defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
callbackUrl?: string
|
||||
record_id: number
|
||||
isBpjs?: boolean
|
||||
}>(), {
|
||||
isBpjs: false,
|
||||
@@ -44,11 +36,7 @@ const { data, isLoading, paginationMeta, searchInput, handlePageChange, handleSe
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const PrbId = typeof route.params.surgery_report_id == 'string' ? parseInt(route.params.surgery_report_id) : 0
|
||||
|
||||
const inputForm = ref<ExposedForm<any> | null>(null)
|
||||
const Prb = ref({})
|
||||
const isConfirmationOpen = ref(false)
|
||||
@@ -60,7 +48,7 @@ provide("isSepDialogOpen", isSepDialogOpen);
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
onMounted(async () => {
|
||||
const result = await getDetail(PrbId)
|
||||
const result = await getDetail(props.record_id)
|
||||
if (result.success) {
|
||||
const responseData = {...result.body.data, date: formatDateYyyyMmDd(result.body.data.date)}
|
||||
Prb.value = responseData
|
||||
@@ -76,7 +64,7 @@ function goBack() {
|
||||
|
||||
async function handleConfirmAdd() {
|
||||
const response = await handleActionEdit(
|
||||
PrbId,
|
||||
props.record_id,
|
||||
await composeFormData(),
|
||||
() => { },
|
||||
() => { },
|
||||
@@ -97,7 +85,7 @@ async function composeFormData(): Promise<Prb> {
|
||||
if (!allValid) return Promise.reject('Form validation failed')
|
||||
|
||||
const formData = input?.values
|
||||
formData.encounter_id = encounterId
|
||||
formData.encounter_id = props.encounter_id
|
||||
return new Promise((resolve) => resolve(formData))
|
||||
}
|
||||
// #endregion region
|
||||
|
||||
@@ -19,13 +19,16 @@ import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
|
||||
// #region State
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter?: Encounter
|
||||
encounter_id: number
|
||||
isBpjs?: boolean
|
||||
redirectToForm?: (myRecord_id?: any) => void
|
||||
redirectToDetail?: (myRecord_id: string|number) => void
|
||||
}>(), {
|
||||
isBpjs: false,
|
||||
redirectToForm: () => { },
|
||||
redirectToDetail: () => { }
|
||||
})
|
||||
const route = useRoute()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const { user } = useUserStore()
|
||||
|
||||
const { data, isLoading, paginationMeta, searchInput, handlePageChange, handleSearch, fetchData } = usePaginatedList({
|
||||
fetchFn: (params) => getList({ ...params, includes: '', }),
|
||||
@@ -66,10 +69,9 @@ const headerPrep: HeaderPrep = {
|
||||
if(true){
|
||||
headerPrep.addNav = {
|
||||
label: "Program Rujuk Balik",
|
||||
onClick: () => navigateTo({
|
||||
name: 'rehab-encounter-id-prb-add',
|
||||
params: { id: encounterId },
|
||||
}),
|
||||
onClick: () => {
|
||||
props.redirectToForm()
|
||||
},
|
||||
};
|
||||
}
|
||||
headerPrep.components = [
|
||||
@@ -134,10 +136,7 @@ provide('isHistoryDialogOpen', isHistoryDialogOpen)
|
||||
watch([recId, recAction, timestamp], () => {
|
||||
switch (recAction.value) {
|
||||
case ActionEvents.showEdit:
|
||||
navigateTo({
|
||||
name: 'rehab-encounter-id-prb-prb_id-edit',
|
||||
params: { id: encounterId, "prb_id": recId.value },
|
||||
})
|
||||
props.redirectToForm(recId.value)
|
||||
break
|
||||
|
||||
case ActionEvents.showPrint:
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import { crudQueryParamsMode } from '~/lib/system-constants';
|
||||
import List from './list.vue'
|
||||
import Detail from './detail.vue'
|
||||
import Entry from './entry.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
encounter_id: number
|
||||
}>()
|
||||
|
||||
const { crudQueryParams, goToDetail, goToEntry } = useQueryCRUD()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<List v-if="crudQueryParams.mode === crudQueryParamsMode.list && !crudQueryParams.recordId"
|
||||
:encounter_id="encounter_id"
|
||||
:redirectToForm="goToEntry"
|
||||
:redirectToDetail="goToDetail"/>
|
||||
<Detail v-else-if="crudQueryParams.mode === crudQueryParamsMode.list && crudQueryParams.recordId"
|
||||
:encounter_id="encounter_id"
|
||||
:record_id="crudQueryParams.recordId"
|
||||
:redirectToForm="goToEntry"/>
|
||||
<Entry v-else-if="crudQueryParams.mode === crudQueryParamsMode.entry"
|
||||
:encounter_id="encounter_id"
|
||||
:record_id="crudQueryParams.recordId" />
|
||||
|
||||
<List v-else :encounter_id="encounter_id" :redirectToForm="goToEntry" :redirectToDetail="goToDetail" />
|
||||
</template>
|
||||
@@ -132,9 +132,20 @@ async function getItems() {
|
||||
|
||||
<Detail :data="data" />
|
||||
|
||||
<div class="mb-5">
|
||||
<div>Keterangan Klinis</div>
|
||||
<Textarea />
|
||||
</div>
|
||||
|
||||
<ItemListEntry
|
||||
:data="items"
|
||||
@requestItem="requestItem"/>
|
||||
|
||||
<div class="my-5">
|
||||
<div>Catatan Pemeriksaan DSA</div>
|
||||
<Textarea />
|
||||
</div>
|
||||
|
||||
<Separator class="my-5" />
|
||||
|
||||
<div class="w-full flex justify-center">
|
||||
|
||||
@@ -13,9 +13,13 @@ import FarmacyHistoryDialog from '~/components/app/resume/history-list/farmacy-h
|
||||
import NationalProgramHistoryDialog from '~/components/app/resume/history-list/national-program-history-dialog.vue';
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
callbackUrl?: string
|
||||
}>()
|
||||
record_id: number
|
||||
}>(), {
|
||||
|
||||
})
|
||||
|
||||
// form related state
|
||||
const personPatientForm = ref<ExposedForm<any> | null>(null)
|
||||
|
||||
@@ -19,18 +19,27 @@ import Confirmation from '~/components/pub/my-ui/confirmation/confirmation.vue'
|
||||
import type { ExposedForm } from '~/types/form'
|
||||
import { VerificationSchema } from '~/schemas/verification.schema'
|
||||
import DocPreviewDialog from '~/components/pub/my-ui/modal/doc-preview-dialog.vue'
|
||||
import type { PagePermission } from '~/models/role'
|
||||
import type { RoleAccesses } from '~/models/role'
|
||||
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
|
||||
import { unauthorizedToast } from '~/lib/utils'
|
||||
import { permissions } from '~/const/page-permission/ambulatory'
|
||||
// #endregion
|
||||
|
||||
|
||||
// #region Permission
|
||||
const roleAccess: PagePermission = PAGE_PERMISSIONS['/rehab/encounter']
|
||||
const roleAccess: RoleAccesses = permissions['/ambulatory/encounter'] || {}
|
||||
const { getPagePermissions } = useRBAC()
|
||||
const pagePermission = getPagePermissions(roleAccess)
|
||||
// #endregion
|
||||
|
||||
// #region State
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
redirectToForm?: (myRecord_id?: any) => void
|
||||
}>(), {
|
||||
redirectToForm: () => { }
|
||||
})
|
||||
|
||||
const { data, isLoading, paginationMeta, searchInput, handlePageChange, handleSearch, fetchData } = usePaginatedList({
|
||||
fetchFn: (params) => getPatients({ ...params, includes: ['person', 'person-Addresses'] }),
|
||||
entityName: 'patient',
|
||||
@@ -67,7 +76,9 @@ const headerPrep: HeaderPrep = {
|
||||
if (pagePermission.canCreate) {
|
||||
headerPrep.addNav = {
|
||||
label: "Resume",
|
||||
onClick: () => navigateTo('/resume/add'),
|
||||
onClick: () => {
|
||||
props.redirectToForm()
|
||||
},
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
@@ -158,6 +169,7 @@ provide('table_data_loader', isLoading)
|
||||
watch([recId, recAction], () => {
|
||||
switch (recAction.value) {
|
||||
case ActionEvents.showVerify:
|
||||
isVerifyDialogOpen.value = true
|
||||
if(pagePermission.canUpdate) {
|
||||
isVerifyDialogOpen.value = true
|
||||
} else {
|
||||
@@ -165,6 +177,7 @@ watch([recId, recAction], () => {
|
||||
}
|
||||
break
|
||||
case ActionEvents.showValidate:
|
||||
isRecordConfirmationOpen.value = true
|
||||
if(pagePermission.canUpdate) {
|
||||
isRecordConfirmationOpen.value = true
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { crudQueryParamsMode } from '~/lib/system-constants';
|
||||
import List from './list.vue'
|
||||
import Entry from './add.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
encounter_id: number
|
||||
}>()
|
||||
|
||||
const { crudQueryParams, goToDetail, goToEntry } = useQueryCRUD()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<List v-if="crudQueryParams.mode === crudQueryParamsMode.list"
|
||||
:encounter_id="encounter_id"
|
||||
:redirectToForm="goToEntry"
|
||||
:redirectToDetail="goToDetail"/>
|
||||
<Entry v-else-if="crudQueryParams.mode === crudQueryParamsMode.entry"
|
||||
:encounter_id="encounter_id"
|
||||
:record_id="crudQueryParams.recordId" />
|
||||
|
||||
<List v-else :encounter_id="encounter_id" :redirectToForm="goToEntry" :redirectToDetail="goToDetail" />
|
||||
</template>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useQueryMode } from '@/composables/useQueryMode'
|
||||
|
||||
import List from './list.vue'
|
||||
import Form from './form.vue'
|
||||
|
||||
// Models
|
||||
import type { Encounter } from '~/models/encounter'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
encounter: Encounter
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const route = useRoute()
|
||||
|
||||
const { mode, goToEntry, backToList } = useQueryCRUDMode('mode')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<List
|
||||
v-if="mode === 'list'"
|
||||
:encounter="props.encounter"
|
||||
@add="goToEntry"
|
||||
@edit="goToEntry"
|
||||
/>
|
||||
<Form
|
||||
v-else
|
||||
@back="backToList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,163 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import Entry from '~/components/app/summary-medic/entry.vue'
|
||||
import Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
||||
import ActionDialog from '~/components/pub/my-ui/nav-footer/ba-su.vue'
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import { SummaryMedicSchema } from '~/schemas/soapi.schema'
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
import { handleActionSave, handleActionEdit } from '~/handlers/soapi-early.handler'
|
||||
// Services
|
||||
import { getDetail } from '~/services/summary-medic.service'
|
||||
const { backToList } = useQueryCRUDMode('mode')
|
||||
const { recordId } = useQueryCRUDRecordId('record-id')
|
||||
|
||||
const route = useRoute()
|
||||
const isOpenProcedure = ref(false)
|
||||
const isOpenDiagnose = ref(false)
|
||||
const isOpenFungsional = ref(false)
|
||||
const procedures = ref([])
|
||||
const diagnoses = ref([])
|
||||
const fungsional = ref([])
|
||||
const selectedProcedure = ref<any>(null)
|
||||
const selectedDiagnose = ref<any>(null)
|
||||
const selectedFungsional = ref<any>(null)
|
||||
const schema = SummaryMedicSchema
|
||||
const payload = ref({
|
||||
typeCode: 'amb-resume',
|
||||
encounter_id: 0,
|
||||
time: '',
|
||||
value: '',
|
||||
})
|
||||
const model = ref({
|
||||
date: '',
|
||||
doctor: '',
|
||||
diagnosis: '',
|
||||
essay: '',
|
||||
plan: '',
|
||||
note: '',
|
||||
})
|
||||
|
||||
const fileUrl = ref('')
|
||||
const isLoading = reactive<DataTableLoader>({
|
||||
isTableLoading: false,
|
||||
})
|
||||
|
||||
async function getDiagnoses() {
|
||||
isLoading.isTableLoading = true
|
||||
const resp = await xfetch('/api/v1/diagnose-src')
|
||||
if (resp.success) {
|
||||
diagnoses.value = (resp.body as Record<string, any>).data
|
||||
}
|
||||
isLoading.isTableLoading = false
|
||||
}
|
||||
|
||||
async function getProcedures() {
|
||||
isLoading.isTableLoading = true
|
||||
const resp = await xfetch('/api/v1/procedure-src')
|
||||
if (resp.success) {
|
||||
procedures.value = (resp.body as Record<string, any>).data
|
||||
}
|
||||
isLoading.isTableLoading = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const mode = route.query.mode
|
||||
const recordId = route.query['record-id']
|
||||
if (mode === 'entry' && recordId) {
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: mapping data detail when edit
|
||||
const loadEntryForEdit = async (id: number) => {
|
||||
const result = await getDetail(id)
|
||||
|
||||
if (result?.success) {
|
||||
console.log('model', model.value)
|
||||
}
|
||||
}
|
||||
|
||||
function handleClick(type: string) {
|
||||
if (type === 'prosedur') {
|
||||
isOpenProcedure.value = true
|
||||
} else if (type === 'diagnosa') {
|
||||
isOpenDiagnose.value = true
|
||||
} else if (type === 'fungsional') {
|
||||
isOpenDiagnose.value = true
|
||||
}
|
||||
}
|
||||
const entryGeneralConsent = ref()
|
||||
async function actionHandler(type: string) {
|
||||
if (type === 'back') {
|
||||
backToList()
|
||||
return
|
||||
}
|
||||
if (type === 'print') {
|
||||
return
|
||||
}
|
||||
const result = await entryGeneralConsent.value?.validate()
|
||||
if (result?.valid) {
|
||||
// if (result.data.relatives.length > 0) {
|
||||
// result.data.relatives = result.data.relatives.map((item: any) => {
|
||||
// return item.name
|
||||
// })
|
||||
// }
|
||||
|
||||
console.log('data', result)
|
||||
const resp = await handleActionSave(
|
||||
{
|
||||
...payload.value,
|
||||
value: JSON.stringify(result.data),
|
||||
encounter_id: +route.params.id,
|
||||
time: new Date().toISOString(),
|
||||
},
|
||||
() => {},
|
||||
() => {},
|
||||
toast,
|
||||
)
|
||||
backToList()
|
||||
} else {
|
||||
console.log('Ada error di form', result)
|
||||
}
|
||||
}
|
||||
|
||||
const icdPreview = ref({
|
||||
procedures: [],
|
||||
diagnoses: [],
|
||||
})
|
||||
|
||||
function actionDialogHandler(type: string) {
|
||||
if (type === 'submit') {
|
||||
// icdPreview.value.procedures = selectedProcedure.value || []
|
||||
// icdPreview.value.diagnoses = selectedDiagnose.value || []
|
||||
// icdPreview.value.fungsional = selectedFungsional.value || []
|
||||
}
|
||||
isOpenProcedure.value = false
|
||||
isOpenDiagnose.value = false
|
||||
}
|
||||
|
||||
provide('table_data_loader', isLoading)
|
||||
provide('icdPreview', icdPreview)
|
||||
</script>
|
||||
<template>
|
||||
<Entry
|
||||
ref="entryGeneralConsent"
|
||||
v-model="model"
|
||||
:schema="schema"
|
||||
@click="handleClick"
|
||||
/>
|
||||
<div class="my-2 flex justify-end py-2">
|
||||
<Action @click="actionHandler" />
|
||||
</div>
|
||||
<Dialog
|
||||
v-model:open="isOpenDiagnose"
|
||||
title="Preview General Content"
|
||||
size="xl"
|
||||
prevent-outside
|
||||
>
|
||||
<embed
|
||||
style="width: 100%; height: 90vh"
|
||||
:src="fileUrl"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,185 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||
import List from '~/components/app/soapi/list.vue'
|
||||
import Entry from '~/components/app/summary-medic/entry.vue'
|
||||
|
||||
// Helpers
|
||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
|
||||
// Types
|
||||
import { ActionEvents, type HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||
import { GeneralConsentSchema, type GeneralConsentFormData } from '~/schemas/general-consent.schema'
|
||||
|
||||
// Handlers
|
||||
import {
|
||||
recId,
|
||||
recAction,
|
||||
recItem,
|
||||
isReadonly,
|
||||
isProcessing,
|
||||
isFormEntryDialogOpen,
|
||||
isRecordConfirmationOpen,
|
||||
onResetState,
|
||||
handleActionSave,
|
||||
handleActionEdit,
|
||||
handleActionRemove,
|
||||
handleCancelForm,
|
||||
} from '~/handlers/summary-medic.handler'
|
||||
|
||||
// Services
|
||||
import { getList, getDetail } from '~/services/soapi-early.service'
|
||||
|
||||
// Models
|
||||
import type { Encounter } from '~/models/encounter'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
encounter: Encounter
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const emits = defineEmits(['add', 'edit'])
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const { goToEntry, backToList } = useQueryCRUDMode('mode')
|
||||
|
||||
let units = ref<{ value: string; label: string }[]>([])
|
||||
const encounterId = ref<number>(props?.encounter?.id || 0)
|
||||
const title = ref('')
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
paginationMeta,
|
||||
searchInput,
|
||||
handlePageChange,
|
||||
handleSearch,
|
||||
fetchData: getMyList,
|
||||
} = usePaginatedList({
|
||||
fetchFn: async ({ page, search }) => {
|
||||
const result = await getList({
|
||||
'encounter-id': route.params.id,
|
||||
includes: 'encounter,employee',
|
||||
'type-code': 'amb-resume',
|
||||
search,
|
||||
page,
|
||||
})
|
||||
if (result.success) {
|
||||
data.value = result.body.data
|
||||
}
|
||||
return { success: result.success || false, body: result.body || {} }
|
||||
},
|
||||
entityName: 'summary-medic',
|
||||
})
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
title: 'Profil Ringkasan Medis',
|
||||
icon: 'i-lucide-box',
|
||||
refSearchNav: {
|
||||
placeholder: 'Cari (min. 3 karakter)...',
|
||||
minLength: 3,
|
||||
debounceMs: 500,
|
||||
showValidationFeedback: true,
|
||||
onInput: (value: string) => {
|
||||
searchInput.value = value
|
||||
},
|
||||
onClick: () => {},
|
||||
onClear: () => {},
|
||||
},
|
||||
addNav: {
|
||||
label: 'Tambah',
|
||||
icon: 'i-lucide-plus',
|
||||
onClick: () => {
|
||||
goToEntry()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const goEdit = (id: string) => {
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
...route.query,
|
||||
mode: 'entry',
|
||||
'record-id': id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const today = new Date()
|
||||
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
|
||||
const getMyDetail = async (id: number | string) => {
|
||||
const result = await getDetail(id)
|
||||
if (result.success) {
|
||||
const currentValue = result.body?.data || {}
|
||||
recItem.value = currentValue
|
||||
isFormEntryDialogOpen.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for row actions when recId or recAction changes
|
||||
watch(recId, () => {
|
||||
console.log('recId', recId.value)
|
||||
if (recAction.value === ActionEvents.showEdit) {
|
||||
goEdit(recId.value)
|
||||
return
|
||||
} else {
|
||||
isRecordConfirmationOpen.value = true
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await getMyList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header
|
||||
v-model="searchInput"
|
||||
:prep="headerPrep"
|
||||
:ref-search-nav="headerPrep.refSearchNav"
|
||||
@search="handleSearch"
|
||||
class="mb-4 xl:mb-5"
|
||||
/>
|
||||
|
||||
<List
|
||||
:data="data"
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
|
||||
<!-- Record Confirmation Modal -->
|
||||
<RecordConfirmation
|
||||
v-model:open="isRecordConfirmationOpen"
|
||||
action="delete"
|
||||
:record="recItem"
|
||||
@confirm="() => handleActionRemove(recId, getMyList, toast)"
|
||||
@cancel=""
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<div class="text-sm">
|
||||
<p>
|
||||
<strong>ID:</strong>
|
||||
{{ record?.id }}
|
||||
</p>
|
||||
<p v-if="record?.name">
|
||||
<strong>Nama:</strong>
|
||||
{{ record.name }}
|
||||
</p>
|
||||
<p v-if="record?.code">
|
||||
<strong>Kode:</strong>
|
||||
{{ record.code }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</RecordConfirmation>
|
||||
</template>
|
||||
@@ -11,17 +11,18 @@ import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import type { SurgeryReport } from '~/models/surgery-report'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
}>()
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
record_id: number
|
||||
}>(), {
|
||||
|
||||
})
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const surgeryReportId = typeof route.params.surgery_report_id == 'string' ? parseInt(route.params.surgery_report_id) : 0
|
||||
const surgeryReport = ref<SurgeryReport | null>(null)
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
@@ -33,11 +34,11 @@ const headerPrep: HeaderPrep = {
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
// onMounted(async () => {
|
||||
// const result = await getDetail(controlLetterId, {
|
||||
// const result = await getDetail(props.record_id, {
|
||||
// includes: "unit,specialist,subspecialist,doctor-employee-person",
|
||||
// })
|
||||
// if (result.success) {
|
||||
// controlLetter.value = result.body?.data
|
||||
// surgeryReport.value = result.body?.data
|
||||
// }
|
||||
// })
|
||||
// #endregion
|
||||
|
||||
@@ -28,9 +28,13 @@ import { handleActionEdit } from '~/handlers/surgery-report.handler'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
callbackUrl?: string
|
||||
}>()
|
||||
record_id: number
|
||||
}>(), {
|
||||
|
||||
})
|
||||
|
||||
// form related state
|
||||
const { data, isLoading, paginationMeta, searchInput, handlePageChange, handleSearch, fetchData } = usePaginatedList({
|
||||
@@ -40,10 +44,7 @@ const { data, isLoading, paginationMeta, searchInput, handlePageChange, handleSe
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const surgeryReportId = typeof route.params.surgery_report_id == 'string' ? parseInt(route.params.surgery_report_id) : 0
|
||||
|
||||
const inputForm = ref<ExposedForm<any> | null>(null)
|
||||
const surgeryReport = ref({})
|
||||
@@ -53,7 +54,7 @@ const selectedOperativeAction = ref<any>(null)
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
onMounted(async () => {
|
||||
const result = await getDetail(surgeryReportId)
|
||||
const result = await getDetail(props.record_id)
|
||||
if (result.success) {
|
||||
const responseData = {...result.body.data, date: formatDateYyyyMmDd(result.body.data.date)}
|
||||
surgeryReport.value = responseData
|
||||
@@ -69,7 +70,7 @@ function goBack() {
|
||||
|
||||
async function handleConfirmAdd() {
|
||||
const response = await handleActionEdit(
|
||||
surgeryReportId,
|
||||
props.record_id,
|
||||
await composeFormData(),
|
||||
() => { },
|
||||
() => { },
|
||||
@@ -90,7 +91,7 @@ async function composeFormData(): Promise<SurgeryReport> {
|
||||
if (!allValid) return Promise.reject('Form validation failed')
|
||||
|
||||
const formData = input?.values
|
||||
formData.encounter_id = encounterId
|
||||
formData.encounter_id = props.encounter_id
|
||||
return new Promise((resolve) => resolve(formData))
|
||||
}
|
||||
// #endregion region
|
||||
|
||||
@@ -16,11 +16,14 @@ import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
// #endregion
|
||||
|
||||
// #region State
|
||||
const props = defineProps<{
|
||||
encounter?: Encounter
|
||||
}>()
|
||||
const route = useRoute()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
redirectToForm?: (myRecord_id?: any) => void
|
||||
redirectToDetail?: (myRecord_id: string|number) => void
|
||||
}>(), {
|
||||
redirectToForm: () => { },
|
||||
redirectToDetail: () => { }
|
||||
})
|
||||
|
||||
const { data, isLoading, paginationMeta, searchInput, handlePageChange, handleSearch, fetchData } = usePaginatedList({
|
||||
fetchFn: (params) => getList({ ...params, includes: 'specialist,subspecialist,doctor-employee-person', }),
|
||||
@@ -42,10 +45,9 @@ const headerPrep: HeaderPrep = {
|
||||
icon: 'i-lucide-history',
|
||||
addNav: {
|
||||
label: "Laporan Operasi",
|
||||
onClick: () => navigateTo({
|
||||
name: 'rehab-encounter-id-surgery-report-add',
|
||||
params: { id: encounterId },
|
||||
}),
|
||||
onClick: () => {
|
||||
props.redirectToForm()
|
||||
},
|
||||
},
|
||||
}
|
||||
const refSearchNav: RefSearchNav = {
|
||||
@@ -125,23 +127,14 @@ provide('isHistoryDialogOpen', isHistoryDialogOpen)
|
||||
watch([recId, recAction], () => {
|
||||
switch (recAction.value) {
|
||||
case ActionEvents.showDetail:
|
||||
navigateTo({
|
||||
name: 'rehab-encounter-id-surgery-report-control_letter_id',
|
||||
params: { id: encounterId, "control_letter_id": recId.value },
|
||||
})
|
||||
props.redirectToDetail(recId.value)
|
||||
break
|
||||
|
||||
case ActionEvents.showEdit:
|
||||
// TODO: Handle edit action
|
||||
// isFormEntryDialogOpen.value = true
|
||||
navigateTo({
|
||||
name: 'rehab-encounter-id-surgery-report-control_letter_id-edit',
|
||||
params: { id: encounterId, "control_letter_id": recId.value },
|
||||
})
|
||||
props.redirectToForm(recId.value)
|
||||
break
|
||||
|
||||
case ActionEvents.showConfirmDelete:
|
||||
// Trigger confirmation modal open
|
||||
isRecordConfirmationOpen.value = true
|
||||
break
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import { crudQueryParamsMode } from '~/lib/system-constants';
|
||||
import List from './list.vue'
|
||||
import Detail from './detail.vue'
|
||||
import Entry from './entry.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
encounter_id: number
|
||||
}>()
|
||||
|
||||
const { crudQueryParams, goToDetail, goToEntry } = useQueryCRUD()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<List v-if="crudQueryParams.mode === crudQueryParamsMode.list && !crudQueryParams.recordId"
|
||||
:encounter_id="encounter_id"
|
||||
:redirectToForm="goToEntry"
|
||||
:redirectToDetail="goToDetail"/>
|
||||
<Detail v-else-if="crudQueryParams.mode === crudQueryParamsMode.list && crudQueryParams.recordId"
|
||||
:encounter_id="encounter_id"
|
||||
:record_id="crudQueryParams.recordId"
|
||||
:redirectToForm="goToEntry"/>
|
||||
<Entry v-else-if="crudQueryParams.mode === crudQueryParamsMode.entry"
|
||||
:encounter_id="encounter_id"
|
||||
:record_id="crudQueryParams.recordId" />
|
||||
|
||||
<List v-else :encounter_id="encounter_id" :redirectToForm="goToEntry" :redirectToDetail="goToDetail" />
|
||||
</template>
|
||||
@@ -11,17 +11,18 @@ import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import type { VaccineData } from '~/models/vaccine-data'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
}>()
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
record_id: number
|
||||
}>(), {
|
||||
|
||||
})
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const VaccineDataId = typeof route.params.surgery_report_id == 'string' ? parseInt(route.params.surgery_report_id) : 0
|
||||
const VaccineData = ref<VaccineData | null>(null)
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { Patient, genPatientProps } from '~/models/patient'
|
||||
import type { ExposedForm } from '~/types/form'
|
||||
import type { PatientBase } from '~/models/patient'
|
||||
import Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import { genPatient } from '~/models/patient'
|
||||
import { PatientSchema } from '~/schemas/patient.schema'
|
||||
import { PersonAddressRelativeSchema } from '~/schemas/person-address-relative.schema'
|
||||
import { PersonAddressSchema } from '~/schemas/person-address.schema'
|
||||
import { PersonContactListSchema } from '~/schemas/person-contact.schema'
|
||||
import { PersonFamiliesSchema } from '~/schemas/person-family.schema'
|
||||
import { ResponsiblePersonSchema } from '~/schemas/person-relative.schema'
|
||||
import { uploadAttachment } from '~/services/patient.service'
|
||||
import { getDetail, update } from '~/services/vaccine-data.service'
|
||||
import type { VaccineData } from '~/models/vaccine-data'
|
||||
import ActionDialog from '~/components/pub/my-ui/nav-footer/ba-su.vue'
|
||||
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
import { withBase } from '~/models/_base'
|
||||
@@ -25,12 +13,13 @@ import { formatDateYyyyMmDd } from '~/lib/date'
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import { getList, remove } from '~/services/vaccine-data.service'
|
||||
import { handleActionEdit, handleActionSave } from '~/handlers/vaccine-data.handler'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
|
||||
import { formatDateToDatetimeLocal } from '~/lib/utils'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = withDefaults(defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter_id: number
|
||||
callbackUrl?: string
|
||||
record_id: number
|
||||
isBpjs?: boolean
|
||||
}>(), {
|
||||
isBpjs: false,
|
||||
@@ -44,11 +33,7 @@ const { data, isLoading, paginationMeta, searchInput, handlePageChange, handleSe
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const VaccineDataId = typeof route.params.surgery_report_id == 'string' ? parseInt(route.params.surgery_report_id) : 0
|
||||
|
||||
const inputForm = ref<ExposedForm<any> | null>(null)
|
||||
const VaccineData = ref({})
|
||||
const isConfirmationOpen = ref(false)
|
||||
@@ -60,7 +45,7 @@ provide("isSepDialogOpen", isSepDialogOpen);
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
onMounted(async () => {
|
||||
const result = await getDetail(VaccineDataId)
|
||||
const result = await getDetail(props.record_id)
|
||||
if (result.success) {
|
||||
const responseData = {...result.body.data, date: formatDateYyyyMmDd(result.body.data.date)}
|
||||
VaccineData.value = responseData
|
||||
@@ -96,7 +81,7 @@ async function composeFormData(): Promise<VaccineData> {
|
||||
if (!allValid) return Promise.reject('Form validation failed')
|
||||
|
||||
const formData = input?.values
|
||||
formData.encounter_id = encounterId
|
||||
formData.encounter_id = props.encounter_id
|
||||
return new Promise((resolve) => resolve(formData))
|
||||
}
|
||||
// #endregion region
|
||||
|
||||
@@ -20,14 +20,16 @@ import { medicalRoles } from '~/const/common/role'
|
||||
|
||||
// #region State
|
||||
const props = withDefaults(defineProps<{
|
||||
encounter?: Encounter
|
||||
encounter_id: number
|
||||
isBpjs?: boolean
|
||||
redirectToForm?: (myRecord_id?: any) => void
|
||||
redirectToDetail?: (myRecord_id: string|number) => void
|
||||
}>(), {
|
||||
isBpjs: false,
|
||||
redirectToForm: () => { },
|
||||
redirectToDetail: () => { }
|
||||
})
|
||||
const route = useRoute()
|
||||
const {user} = useUserStore()
|
||||
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const { user } = useUserStore()
|
||||
|
||||
const { data, isLoading, paginationMeta, searchInput, handlePageChange, handleSearch, fetchData } = usePaginatedList({
|
||||
fetchFn: (params) => getList({ ...params, includes: '', }),
|
||||
@@ -61,10 +63,9 @@ const headerPrep: HeaderPrep = {
|
||||
if(user.activeRole === 'emp|doc' || user.activeRole === 'system') {
|
||||
headerPrep.addNav = {
|
||||
label: "Data Vaksin",
|
||||
onClick: () => navigateTo({
|
||||
name: 'rehab-encounter-id-vaccine-data-add',
|
||||
params: { id: encounterId },
|
||||
}),
|
||||
onClick: () => {
|
||||
props.redirectToForm()
|
||||
},
|
||||
};
|
||||
}
|
||||
// #endregion
|
||||
@@ -123,10 +124,7 @@ provide('isHistoryDialogOpen', isHistoryDialogOpen)
|
||||
watch([recId, recAction, timestamp], () => {
|
||||
switch (recAction.value) {
|
||||
case ActionEvents.showDetail:
|
||||
navigateTo({
|
||||
name: 'rehab-encounter-id-vaccine-data-vaccine_data_id',
|
||||
params: { id: encounterId, "vaccine_data_id": recId.value },
|
||||
})
|
||||
props.redirectToDetail(recId.value)
|
||||
break
|
||||
|
||||
case ActionEvents.showConfirmDelete:
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import { crudQueryParamsMode } from '~/lib/system-constants';
|
||||
import List from './list.vue'
|
||||
import Detail from './detail.vue'
|
||||
import Entry from './entry.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
encounter_id: number
|
||||
}>()
|
||||
|
||||
const { crudQueryParams, goToDetail, goToEntry } = useQueryCRUD()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<List v-if="crudQueryParams.mode === crudQueryParamsMode.list && !crudQueryParams.recordId"
|
||||
:encounter_id="encounter_id"
|
||||
:redirectToForm="goToEntry"
|
||||
:redirectToDetail="goToDetail"/>
|
||||
<Detail v-else-if="crudQueryParams.mode === crudQueryParamsMode.list && crudQueryParams.recordId"
|
||||
:encounter_id="encounter_id"
|
||||
:record_id="crudQueryParams.recordId"
|
||||
:redirectToForm="goToEntry"/>
|
||||
<Entry v-else-if="crudQueryParams.mode === crudQueryParamsMode.entry"
|
||||
:encounter_id="encounter_id"
|
||||
:record_id="crudQueryParams.recordId" />
|
||||
|
||||
<List v-else :encounter_id="encounter_id" :redirectToForm="goToEntry" :redirectToDetail="goToDetail" />
|
||||
</template>
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import type { Item } from './index'
|
||||
|
||||
const props = defineProps<{
|
||||
items: Item[]
|
||||
useFlex?: boolean
|
||||
}>()
|
||||
|
||||
const model = defineModel<string[]>()
|
||||
const checks: Record<string, any> = ref({})
|
||||
|
||||
model.value = []
|
||||
props.items.forEach((item) => {
|
||||
checks.value[item.value] = item.checked || false
|
||||
})
|
||||
|
||||
watch(checks, () => {
|
||||
const list: string[] = []
|
||||
Object.keys(checks.value).forEach((key) => {
|
||||
if (checks.value[key]) {
|
||||
list.push(key)
|
||||
}
|
||||
})
|
||||
model.value = list
|
||||
}, { deep: true })
|
||||
|
||||
function check(value: string, status: boolean) {
|
||||
checks.value[value] = status
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="useFlex ? 'flex gap-2' : ''">
|
||||
<label v-for="item, idx in items" :key="item.value" class="flex pe-4">
|
||||
<Checkbox @update:checked="(status) => check(item.value, status)" :checked="checks[item.value]" />
|
||||
<div class="pt-0.5 ps-2">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,14 @@
|
||||
export interface Item {
|
||||
value: string
|
||||
label: string
|
||||
checked?: boolean
|
||||
}
|
||||
|
||||
export function checkItems(items: Item[], value: string[]) {
|
||||
items.forEach((item, idx) => {
|
||||
items[idx]!.checked = value.includes(item.value)
|
||||
// item.checked = value.includes(item.value)
|
||||
})
|
||||
}
|
||||
|
||||
export { default as Checkboxes } from './checkboxes.vue'
|
||||
@@ -4,7 +4,7 @@ import { type Item } from './index'
|
||||
|
||||
const props = defineProps<{
|
||||
id?: string
|
||||
modelValue?: string
|
||||
modelValue?: string | number
|
||||
items: Item[]
|
||||
placeholder?: string
|
||||
searchPlaceholder?: string
|
||||
@@ -16,8 +16,8 @@ const props = defineProps<{
|
||||
const model = defineModel()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
'update:searchText': [value: string]
|
||||
'update:modelValue': [value: string | number]
|
||||
'update:searchText': [value: string | number]
|
||||
}>()
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface Item {
|
||||
value: string
|
||||
value: string | number
|
||||
label: string
|
||||
code?: string
|
||||
priority?: number
|
||||
@@ -7,12 +7,12 @@ export interface Item {
|
||||
|
||||
export function recStrToItem(input: Record<string, string>): Item[] {
|
||||
const items: Item[] = []
|
||||
let idx = 0;
|
||||
let idx = 0
|
||||
for (const key in input) {
|
||||
if (input.hasOwnProperty(key)) {
|
||||
items.push({
|
||||
value: key || ('unknown-' + idx),
|
||||
label: input[key] || ('unknown-' + idx),
|
||||
value: key || 'unknown-' + idx,
|
||||
label: input[key] || 'unknown-' + idx,
|
||||
})
|
||||
}
|
||||
idx++
|
||||
|
||||
@@ -1,33 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
height?: number
|
||||
class?: string
|
||||
activeTab?: 1 | 2
|
||||
}>()
|
||||
|
||||
const classVal = computed(() => {
|
||||
return props.class ? props.class : ''
|
||||
})
|
||||
const activeTab = ref(props.activeTab || 1)
|
||||
|
||||
function switchActiveTab() {
|
||||
activeTab.value = activeTab.value === 1 ? 2 : 1
|
||||
function handleClick(value: 1 | 2) {
|
||||
activeTab.value = value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="`content-switcher ${classVal}`" :style="height ? `height:${200}px` : ''">
|
||||
<div class="wrapper">
|
||||
<div :class="`item item-1 ${activeTab === 1 ? 'active' : 'inactive'}`">
|
||||
<slot name="content1" />
|
||||
<div class="content-switcher" :style="`height: ${height || 200}px`">
|
||||
<div :class="`${activeTab === 1 ? 'active' : 'inactive'}`">
|
||||
<div class="content-wrapper">
|
||||
<div>
|
||||
<slot name="content1" />
|
||||
</div>
|
||||
</div>
|
||||
<div :class="`nav border-slate-300 ${ activeTab == 1 ? 'border-l' : 'border-r'}`">
|
||||
<button @click="switchActiveTab()" class="!p-0 w-full h-full">
|
||||
<Icon :name="activeTab == 1 ? 'i-lucide-chevron-left' : 'i-lucide-chevron-right'" class="text-3xl" />
|
||||
<div class="content-nav">
|
||||
<button @click="handleClick(1)">
|
||||
<Icon name="i-lucide-chevron-right" />
|
||||
</button>
|
||||
</div>
|
||||
<div :class="`item item-2 ${activeTab === 2 ? 'active' : 'inactive'}`">
|
||||
<slot name="content2" />
|
||||
</div>
|
||||
<div :class="`${activeTab === 2 ? 'active' : 'inactive'}`">
|
||||
<div class="content-nav">
|
||||
<button @click="handleClick(2)">
|
||||
<Icon name="i-lucide-chevron-left" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="content-wrapper">
|
||||
<div>
|
||||
<slot name="content2" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -35,24 +42,45 @@ function switchActiveTab() {
|
||||
|
||||
<style>
|
||||
.content-switcher {
|
||||
@apply overflow-hidden
|
||||
@apply flex overflow-hidden gap-3
|
||||
}
|
||||
.wrapper {
|
||||
@apply flex w-[200%] h-full
|
||||
}
|
||||
.item {
|
||||
@apply w-[calc(50%-60px)]
|
||||
.content-switcher > * {
|
||||
@apply border border-slate-300 rounded-md flex overflow-hidden
|
||||
}
|
||||
|
||||
.item-1.active {
|
||||
@apply ms-0 transition-all duration-500 ease-in-out
|
||||
.content-wrapper {
|
||||
@apply p-4 2xl:p-5 overflow-hidden grow
|
||||
}
|
||||
.item-1.inactive {
|
||||
@apply -ms-[calc(50%-60px)] transition-all duration-500 ease-in-out
|
||||
.inactive .content-wrapper {
|
||||
@apply p-0 w-0
|
||||
}
|
||||
|
||||
.nav {
|
||||
@apply h-full w-[60px] flex flex-row items-center justify-center content-center !text-2xl overflow-hidden
|
||||
.content-nav {
|
||||
@apply h-full flex flex-row items-center justify-center content-center !text-2xl overflow-hidden
|
||||
}
|
||||
|
||||
.content-nav button {
|
||||
@apply pt-2 px-2 h-full w-full
|
||||
}
|
||||
|
||||
/* .content-switcher .inactive > .content-wrapper {
|
||||
@apply w-0 p-0 opacity-0 transition-all duration-500 ease-in-out
|
||||
} */
|
||||
.content-switcher .inactive {
|
||||
@apply w-16 transition-all duration-500 ease-in-out
|
||||
}
|
||||
.content-switcher .inactive > .content-nav {
|
||||
@apply w-full transition-all duration-100 ease-in-out
|
||||
}
|
||||
|
||||
.content-switcher .active {
|
||||
@apply grow transition-all duration-500 ease-in-out
|
||||
}
|
||||
.content-switcher .active > .content-nav {
|
||||
@apply w-0 transition-all duration-100 ease-in-out
|
||||
}
|
||||
/* .content-switcher .active > .content-wrapper {
|
||||
@apply w-full delay-1000 transition-all duration-1000 ease-in-out
|
||||
} */
|
||||
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import type { LinkItem, ListItemDto } from './types'
|
||||
import { ActionEvents } from './types'
|
||||
|
||||
const props = defineProps<{
|
||||
rec: ListItemDto
|
||||
}>()
|
||||
|
||||
const recId = inject<Ref<number>>('rec_id')!
|
||||
const recAction = inject<Ref<string>>('rec_action')!
|
||||
const recItem = inject<Ref<any>>('rec_item')!
|
||||
const timestamp = inject<Ref<number>>('timestamp')!
|
||||
const activeKey = ref<string | null>(null)
|
||||
const linkItems: LinkItem[] = [
|
||||
{
|
||||
label: 'Detail',
|
||||
onClick: () => {
|
||||
detail()
|
||||
},
|
||||
icon: 'i-lucide-eye',
|
||||
},
|
||||
]
|
||||
|
||||
function detail() {
|
||||
recId.value = props.rec.id || 0
|
||||
recAction.value = ActionEvents.showDetail
|
||||
recItem.value = props.rec
|
||||
timestamp.value = Date.now()
|
||||
}
|
||||
</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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user