wip: init form entry education assessment
import clinical const from sheets wip: form entry add education assessment done: checkbox wip: add select Asesmen Kemampuan dan Kemauan Belajar
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
<script setup lang="ts">
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { Form } from '~/components/pub/ui/form'
|
||||
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
import Separator from '~/components/pub/ui/separator/Separator.vue'
|
||||
|
||||
import { BaseSelect, BaseTextarea, CheckboxGeneral, CheckboxSpecial } from './field'
|
||||
|
||||
// constant
|
||||
import {
|
||||
abilityCode,
|
||||
willCode,
|
||||
medObstacleCode,
|
||||
learnMethodCode,
|
||||
langClassCode,
|
||||
translatorSrcCode,
|
||||
} from '~/lib/clinical.constants'
|
||||
|
||||
const props = defineProps<{
|
||||
schema: any
|
||||
initialValues?: any
|
||||
}>()
|
||||
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
const formRef = ref()
|
||||
|
||||
defineExpose({
|
||||
validate: () => formRef.value?.validate(),
|
||||
resetForm: () => formRef.value?.resetForm(),
|
||||
setValues: (values: any, shouldValidate = true) => formRef.value?.setValues(values, shouldValidate),
|
||||
values: computed(() => formRef.value?.values),
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form
|
||||
ref="formRef"
|
||||
v-slot="{ values }"
|
||||
as=""
|
||||
keep-values
|
||||
:validation-schema="formSchema"
|
||||
:validate-on-mount="false"
|
||||
validation-mode="onSubmit"
|
||||
:initial-values="initialValues ? initialValues : {}"
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
<CheckboxSpecial
|
||||
field-name="specificEducationNeeds"
|
||||
label="Edukasi Khusus"
|
||||
:col-span="2"
|
||||
/>
|
||||
</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"
|
||||
/>
|
||||
|
||||
<BaseSelect
|
||||
field-name="learningWillingness"
|
||||
label="Kemauan Belajar"
|
||||
:items="willCode"
|
||||
/>
|
||||
|
||||
<BaseSelect
|
||||
field-name="barrier"
|
||||
label="Hambatan"
|
||||
:items="medObstacleCode"
|
||||
/>
|
||||
|
||||
<BaseSelect
|
||||
field-name="learningMethod"
|
||||
label="Metode Pembelajaran"
|
||||
:items="learnMethodCode"
|
||||
/>
|
||||
|
||||
<BaseSelect
|
||||
field-name="language"
|
||||
label="Bahasa"
|
||||
:items="langClassCode"
|
||||
/>
|
||||
|
||||
<BaseSelect
|
||||
field-name="languageBarrier"
|
||||
label="Hambatan Bahasa"
|
||||
:items="translatorSrcCode"
|
||||
/>
|
||||
<BaseSelect
|
||||
field-name="beliefValue"
|
||||
label="Keyakinan pada Nilai-Nilai yang Dianut"
|
||||
:items="{
|
||||
ya: 'IYA',
|
||||
tidak: 'TIDAK',
|
||||
}"
|
||||
/>
|
||||
</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>
|
||||
<BaseTextarea
|
||||
field-name="recommendation"
|
||||
label="Plan A"
|
||||
/>
|
||||
<BaseTextarea
|
||||
field-name="recommendation"
|
||||
label="Plan B"
|
||||
/>
|
||||
<BaseTextarea
|
||||
field-name="recommendation"
|
||||
label="Plan Plan Pak Supir"
|
||||
/>
|
||||
</DE.Block>
|
||||
</Form>
|
||||
</template>
|
||||
@@ -0,0 +1,64 @@
|
||||
<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[]
|
||||
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
|
||||
: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,62 @@
|
||||
<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 religionOptions = mapToComboboxOptList(props.items)
|
||||
</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="religionOptions"
|
||||
: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,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,23 @@
|
||||
<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
|
||||
colSpan?: number
|
||||
}
|
||||
defineProps<Props>()
|
||||
|
||||
const generalItems = mapToCheckItems(generalEduCode)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseCheckbox
|
||||
:field-name="fieldName"
|
||||
:label="label"
|
||||
: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,5 @@
|
||||
export { default as BaseSelect } from './base-select.vue'
|
||||
export { default as BaseTextarea } from './base-textarea.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,72 @@
|
||||
<script setup lang="ts">
|
||||
import * as z from 'zod'
|
||||
import type { ExposedForm } from '~/types/form'
|
||||
|
||||
import Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
||||
|
||||
const schema = z.object({
|
||||
generalEducationNeeds: z
|
||||
.array(z.string(), {
|
||||
required_error: 'Mohon pilih setidaknya item',
|
||||
})
|
||||
.min(1, 'Mohon pilih setidaknya item'),
|
||||
|
||||
specificEducationNeeds: z
|
||||
.array(z.string(), {
|
||||
required_error: 'Mohon pilih setidaknya item',
|
||||
})
|
||||
.min(1, 'Mohon pilih setidaknya item'),
|
||||
|
||||
learningAbility: z.string({
|
||||
required_error: 'Mohon pilih kemampuan belajar',
|
||||
}),
|
||||
|
||||
learningWillingness: z.string({
|
||||
required_error: 'Mohon pilih kemauan belajar',
|
||||
}),
|
||||
|
||||
barrier: z.string({
|
||||
required_error: 'Mohon pilih hambatan',
|
||||
}),
|
||||
|
||||
learningMethod: z.string({
|
||||
required_error: 'Mohon pilih metode pembelajaran',
|
||||
}),
|
||||
|
||||
language: z.string({
|
||||
required_error: 'Mohon pilih bahasa',
|
||||
}),
|
||||
|
||||
languageBarrier: z.string({
|
||||
required_error: 'Mohon pilih hambatan bahasa',
|
||||
}),
|
||||
|
||||
beliefValue: z.string({
|
||||
required_error: 'Mohon pilih keyakinan pada nilai-nilai yang dianut',
|
||||
}),
|
||||
})
|
||||
|
||||
const form = ref<ExposedForm<any> | null>(null)
|
||||
|
||||
async function handleActionClick(eventType: string) {
|
||||
if (eventType === 'submit') {
|
||||
const isValid = await form.value?.validate()
|
||||
console.log(isValid)
|
||||
}
|
||||
|
||||
if (eventType === 'cancel') {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg font-semibold xl:text-xl">Tambah Asesmen Edukasi</div>
|
||||
<AppAssessmentEducationEntryForm
|
||||
ref="form"
|
||||
:schema="schema"
|
||||
/>
|
||||
|
||||
<div class="my-2 flex justify-end py-2">
|
||||
<Action @click="handleActionClick" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,87 @@
|
||||
export const generalEduCode = {
|
||||
'right-obg': 'Hak dan kewajiban pasien dan keluarga',
|
||||
'general-consent': 'General Consent',
|
||||
service: 'Pelayanan yang disediakan (jam pelayanan, akses pelayanan dan proses pelayanan)',
|
||||
'all-care-service': 'Sumber alternatif asuhan di tempat lain/faskes lain',
|
||||
'home-plan': 'Rencana tindakan di rumah',
|
||||
'home-care': 'Kebutuhan perawatan di rumah',
|
||||
orientation: 'Orientasi Ruangan',
|
||||
'fall-risk-prevention': 'Pencegahan risiko jatuh',
|
||||
'alt-care': 'Alternatif pelayanan',
|
||||
'act-delay': 'Penundaan Tindakan',
|
||||
others: 'Lain-lain',
|
||||
} as const
|
||||
|
||||
export type GeneralEduCodeKey = keyof typeof generalEduCode
|
||||
|
||||
export const specialEduCode = {
|
||||
'disease-diag-dev': 'Diagnosa penyakit dan perkembangannya',
|
||||
'safe-med-usage': 'Penggunaan obat yang aman',
|
||||
'side-effect': 'Efek samping dan reaksi obat',
|
||||
diet: 'Diet/Nutrisi',
|
||||
'pain-mgmt': 'Manajemen nyeri',
|
||||
'medical-eq-usage': 'Penggunaan Peralatan Medis',
|
||||
'rehab-technique': 'Teknik Rehabilitasi',
|
||||
'prevention-act': 'Tindakan pencegahan (cuci tangan, pemasangan gelang)',
|
||||
} as const
|
||||
|
||||
export type SpecialEduCodeKey = keyof typeof specialEduCode
|
||||
|
||||
export const eduAssessmentCode = {
|
||||
'learn-ability': 'Kemampuan Belajar',
|
||||
'learn-will': 'Kemauan Belajar',
|
||||
obstacle: 'Hambatan',
|
||||
'learn-method': 'Metode Pembelajaran',
|
||||
lang: 'Bahasa',
|
||||
'lang-obstacle': 'Hambatan Bahasa',
|
||||
belief: 'Keyakinan',
|
||||
} as const
|
||||
|
||||
export type EduAssessmentCodeKey = keyof typeof eduAssessmentCode
|
||||
|
||||
export const abilityCode = {
|
||||
able: 'Mampu',
|
||||
'not-able': 'Tidak Mampu',
|
||||
} as const
|
||||
|
||||
export type AbilityCodeKey = keyof typeof abilityCode
|
||||
|
||||
export const willCode = {
|
||||
ready: 'Siap',
|
||||
interested: 'Tertarik',
|
||||
'not-interested': 'Tidak Tertarik',
|
||||
} as const
|
||||
|
||||
export type WillCodeKey = keyof typeof willCode
|
||||
|
||||
export const medObstacleCode = {
|
||||
hearing: 'Pendengaran',
|
||||
sight: 'Penglihatan',
|
||||
physical: 'Fisik',
|
||||
emotional: 'Emosional',
|
||||
cognitif: 'Kognitif',
|
||||
} as const
|
||||
|
||||
export type MedObstacleCodeKey = keyof typeof medObstacleCode
|
||||
|
||||
export const learnMethodCode = {
|
||||
demo: 'Demonstrasi',
|
||||
'discuss-leaflet': 'Diskusi Leaflet',
|
||||
} as const
|
||||
|
||||
export type LearnMethodCodeKey = keyof typeof learnMethodCode
|
||||
|
||||
export const langClassCode = {
|
||||
ind: 'Indonesia',
|
||||
region: 'Daerah',
|
||||
foreign: 'Asing',
|
||||
} as const
|
||||
|
||||
export type LangClassCodeKey = keyof typeof langClassCode
|
||||
|
||||
export const translatorSrcCode = {
|
||||
team: 'Tim Penerjemah',
|
||||
family: 'Keluarga',
|
||||
} as const
|
||||
|
||||
export type TranslatorSrcCodeKey = keyof typeof translatorSrcCode
|
||||
+13
-1
@@ -7,6 +7,10 @@ export interface SelectOptionType<_T = string> {
|
||||
label: string
|
||||
code?: string
|
||||
}
|
||||
export interface CheckItem {
|
||||
id: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
@@ -27,6 +31,15 @@ export function mapToComboboxOptList(items: Record<string, string>): SelectOptio
|
||||
return result
|
||||
}
|
||||
|
||||
export function mapToCheckItems<T extends Record<string, string>, K extends keyof T & string>(
|
||||
items: T,
|
||||
): { id: K; label: T[K] }[] {
|
||||
return Object.entries(items).map(([key, value]) => ({
|
||||
id: key as K,
|
||||
label: value as T[K],
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Mengkonversi string menjadi title case (huruf pertama setiap kata kapital)
|
||||
* @param str - String yang akan dikonversi
|
||||
@@ -36,7 +49,6 @@ export function toTitleCase(str: string): string {
|
||||
return str.toLowerCase().replace(/\b\w/g, (char) => char.toUpperCase())
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Menghitung umur berdasarkan tanggal lahir
|
||||
* @param birthDate - Tanggal lahir dalam format Date atau string
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import type { PagePermission } from '~/models/role'
|
||||
import Error from '~/components/pub/my-ui/error/error.vue'
|
||||
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['rbac'],
|
||||
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
||||
title: 'Tambah Assessment Education',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
useHead({
|
||||
title: () => route.meta.title as string,
|
||||
})
|
||||
|
||||
const roleAccess: PagePermission = PAGE_PERMISSIONS['/patient']
|
||||
|
||||
const { checkRole, hasReadAccess } = useRBAC()
|
||||
|
||||
// Check if user has access to this page
|
||||
// const hasAccess = checkRole(roleAccess)
|
||||
const hasAccess = true
|
||||
if (!hasAccess) {
|
||||
navigateTo('/403')
|
||||
}
|
||||
|
||||
// Define permission-based computed properties
|
||||
const canRead = hasReadAccess(roleAccess)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="canRead">
|
||||
<ContentAssessmentEducationAdd />
|
||||
</div>
|
||||
<Error
|
||||
v-else
|
||||
:status-code="403"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user