Merge branch 'dev' of github.com:dikstub-rssa/simrs-fe into dev
This commit is contained in:
No files matched your search
+6
-22
@@ -1,33 +1,17 @@
|
|||||||
# Build Stage
|
# Build stage
|
||||||
FROM node:20-alpine AS build-stage
|
FROM node:20-alpine AS build-stage
|
||||||
|
|
||||||
# Set the working directory inside the container
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Enable pnpm using corepack
|
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
# Copy pnpm related files and package.json to leverage Docker layer caching
|
|
||||||
COPY package.json pnpm-lock.yaml ./
|
COPY package.json pnpm-lock.yaml ./
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
# Install dependencies using pnpm
|
|
||||||
# Using --frozen-lockfile ensures consistent installations based on pnpm-lock.yaml
|
|
||||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.pnpm-store pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
# Copy the rest of the application files
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
RUN pnpm generate
|
||||||
|
|
||||||
# Build the Vue.js application for production
|
# Production stage
|
||||||
RUN pnpm build
|
FROM nginx:stable-alpine
|
||||||
|
COPY --from=build-stage /app/.output/public /usr/share/nginx/html
|
||||||
|
|
||||||
# Production Stage
|
|
||||||
FROM nginx:stable-alpine AS production-stage
|
|
||||||
|
|
||||||
# Copy the built Vue.js application from the build stage to Nginx's web root
|
|
||||||
COPY --from=build-stage /app/dist /usr/share/nginx/html
|
|
||||||
|
|
||||||
# Expose port 80 for Nginx
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
# Command to run Nginx in the foreground
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
File diff suppressed because it is too large
Load diff
@@ -11,6 +11,7 @@ import { calculateAge } from '~/models/person'
|
|||||||
// components
|
// components
|
||||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
import { InputBase, FileField as FileUpload } from '~/components/pub/my-ui/form'
|
import { InputBase, FileField as FileUpload } from '~/components/pub/my-ui/form'
|
||||||
|
import { Skeleton } from '~/components/pub/ui/skeleton'
|
||||||
import { SelectBirthPlace } from '~/components/app/person/fields'
|
import { SelectBirthPlace } from '~/components/app/person/fields'
|
||||||
import {
|
import {
|
||||||
InputName,
|
InputName,
|
||||||
@@ -60,10 +61,20 @@ interface PatientFormInput {
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isReadonly: boolean
|
isReadonly: boolean
|
||||||
|
languageOptions?: { value: string; label: string }[]
|
||||||
|
ethnicOptions?: { value: string; label: string }[]
|
||||||
initialValues?: PatientFormInput
|
initialValues?: PatientFormInput
|
||||||
|
mode?: 'add' | 'edit'
|
||||||
|
identityFileUrl?: string
|
||||||
|
familyCardFileUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'preview', url: string): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const formSchema = toTypedSchema(PatientSchema)
|
const formSchema = toTypedSchema(PatientSchema)
|
||||||
|
|
||||||
const { values, resetForm, setValues, setFieldValue, validate, setFieldError } = useForm<FormData>({
|
const { values, resetForm, setValues, setFieldValue, validate, setFieldError } = useForm<FormData>({
|
||||||
@@ -208,6 +219,7 @@ watch(
|
|||||||
label="Suku"
|
label="Suku"
|
||||||
placeholder="Pilih suku bangsa"
|
placeholder="Pilih suku bangsa"
|
||||||
:is-disabled="isReadonly || values.nationality !== 'WNI'"
|
:is-disabled="isReadonly || values.nationality !== 'WNI'"
|
||||||
|
:items="ethnicOptions || []"
|
||||||
/>
|
/>
|
||||||
<SelectLanguage
|
<SelectLanguage
|
||||||
field-name="language"
|
field-name="language"
|
||||||
@@ -215,6 +227,7 @@ watch(
|
|||||||
placeholder="Pilih preferensi bahasa"
|
placeholder="Pilih preferensi bahasa"
|
||||||
is-required
|
is-required
|
||||||
:is-disabled="isReadonly"
|
:is-disabled="isReadonly"
|
||||||
|
:items="languageOptions || []"
|
||||||
/>
|
/>
|
||||||
<SelectReligion
|
<SelectReligion
|
||||||
field-name="religion"
|
field-name="religion"
|
||||||
@@ -256,22 +269,112 @@ watch(
|
|||||||
:col-count="2"
|
:col-count="2"
|
||||||
:cell-flex="false"
|
:cell-flex="false"
|
||||||
>
|
>
|
||||||
<FileUpload
|
<div class="flex flex-col gap-3">
|
||||||
field-name="identityCardFile"
|
<FileUpload
|
||||||
label="Dokumen KTP"
|
field-name="residentIdentityFile"
|
||||||
placeholder="Unggah scan dokumen KTP"
|
label="Dokumen KTP"
|
||||||
:accept="['pdf', 'jpg', 'png']"
|
placeholder="Unggah scan dokumen KTP"
|
||||||
:max-size-mb="1"
|
:accept="['pdf', 'jpg', 'png']"
|
||||||
:is-disabled="isReadonly"
|
:max-size-mb="1"
|
||||||
/>
|
:is-disabled="isReadonly"
|
||||||
<FileUpload
|
/>
|
||||||
field-name="familyCardFile"
|
<!-- Embed Preview Dokumen KTP (mode edit) -->
|
||||||
label="Dokumen KK"
|
<div
|
||||||
placeholder="Unggah scan dokumen KK"
|
v-if="mode === 'edit'"
|
||||||
:accept="['pdf', 'jpg', 'png']"
|
class="flex flex-col gap-1"
|
||||||
:max-size-mb="1"
|
>
|
||||||
:is-disabled="isReadonly"
|
<span class="text-xs text-muted-foreground">Dokumen Tersimpan</span>
|
||||||
/>
|
<div
|
||||||
|
class="relative h-28 w-48 cursor-pointer overflow-hidden rounded-md border bg-muted"
|
||||||
|
:class="{ 'cursor-not-allowed opacity-60': !identityFileUrl }"
|
||||||
|
@click="identityFileUrl && emit('preview', identityFileUrl)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="identityFileUrl"
|
||||||
|
:src="identityFileUrl"
|
||||||
|
alt="Dokumen KTP"
|
||||||
|
class="h-full w-full object-cover"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
v-else
|
||||||
|
class="h-full w-full"
|
||||||
|
/>
|
||||||
|
<!-- Overlay -->
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 flex items-center justify-center bg-black/50 transition-opacity"
|
||||||
|
:class="identityFileUrl ? 'opacity-0 hover:opacity-100' : 'opacity-100'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="identityFileUrl"
|
||||||
|
class="flex flex-col items-center gap-1 text-white"
|
||||||
|
>
|
||||||
|
<span class="i-lucide-eye h-5 w-5" />
|
||||||
|
<span class="text-xs font-medium">Lihat Dokumen</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="flex flex-col items-center gap-1 text-white/70"
|
||||||
|
>
|
||||||
|
<span class="i-lucide-info h-5 w-5" />
|
||||||
|
<span class="text-xs font-medium">No Data</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<FileUpload
|
||||||
|
field-name="familyIdentityFile"
|
||||||
|
label="Dokumen KK"
|
||||||
|
placeholder="Unggah scan dokumen KK"
|
||||||
|
:accept="['pdf', 'jpg', 'png']"
|
||||||
|
:max-size-mb="1"
|
||||||
|
:is-disabled="isReadonly"
|
||||||
|
/>
|
||||||
|
<!-- Embed Preview Dokumen KK (mode edit) -->
|
||||||
|
<div
|
||||||
|
v-if="mode === 'edit'"
|
||||||
|
class="flex flex-col gap-1"
|
||||||
|
>
|
||||||
|
<span class="text-xs text-muted-foreground">Dokumen Tersimpan</span>
|
||||||
|
<div
|
||||||
|
class="relative h-28 w-48 cursor-pointer overflow-hidden rounded-md border bg-muted"
|
||||||
|
:class="{ 'cursor-not-allowed opacity-60': !familyCardFileUrl }"
|
||||||
|
@click="familyCardFileUrl && emit('preview', familyCardFileUrl)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="familyCardFileUrl"
|
||||||
|
:src="familyCardFileUrl"
|
||||||
|
alt="Dokumen KK"
|
||||||
|
class="h-full w-full object-cover"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
v-else
|
||||||
|
class="h-full w-full"
|
||||||
|
/>
|
||||||
|
<!-- Overlay -->
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 flex items-center justify-center bg-black/50 transition-opacity"
|
||||||
|
:class="familyCardFileUrl ? 'opacity-0 hover:opacity-100' : 'opacity-100'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="familyCardFileUrl"
|
||||||
|
class="flex flex-col items-center gap-1 text-white"
|
||||||
|
>
|
||||||
|
<span class="i-lucide-eye h-5 w-5" />
|
||||||
|
<span class="text-xs font-medium">Lihat Dokumen</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="flex flex-col items-center gap-1 text-white/70"
|
||||||
|
>
|
||||||
|
<span class="i-lucide-info h-5 w-5" />
|
||||||
|
<span class="text-xs font-medium">No Data</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</DE.Block>
|
</DE.Block>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
@@ -6,6 +6,7 @@ import { cn } from '~/lib/utils'
|
|||||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
items: { value: string; label: string }[]
|
||||||
fieldName?: string
|
fieldName?: string
|
||||||
label?: string
|
label?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
@@ -28,23 +29,23 @@ const {
|
|||||||
labelClass,
|
labelClass,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const ethnicOptions = [
|
// const ethnicOptions = [
|
||||||
{ label: 'Tidak diketahui', value: 'unknown', priority: 1 },
|
// { label: 'Tidak diketahui', value: 'unknown', priority: 1 },
|
||||||
{ label: 'Jawa', value: 'jawa' },
|
// { label: 'Jawa', value: 'jawa' },
|
||||||
{ label: 'Sunda', value: 'sunda' },
|
// { label: 'Sunda', value: 'sunda' },
|
||||||
{ label: 'Batak', value: 'batak' },
|
// { label: 'Batak', value: 'batak' },
|
||||||
{ label: 'Betawi', value: 'betawi' },
|
// { label: 'Betawi', value: 'betawi' },
|
||||||
{ label: 'Minangkabau', value: 'minangkabau' },
|
// { label: 'Minangkabau', value: 'minangkabau' },
|
||||||
{ label: 'Bugis', value: 'bugis' },
|
// { label: 'Bugis', value: 'bugis' },
|
||||||
{ label: 'Madura', value: 'madura' },
|
// { label: 'Madura', value: 'madura' },
|
||||||
{ label: 'Banjar', value: 'banjar' },
|
// { label: 'Banjar', value: 'banjar' },
|
||||||
{ label: 'Bali', value: 'bali' },
|
// { label: 'Bali', value: 'bali' },
|
||||||
{ label: 'Dayak', value: 'dayak' },
|
// { label: 'Dayak', value: 'dayak' },
|
||||||
{ label: 'Aceh', value: 'aceh' },
|
// { label: 'Aceh', value: 'aceh' },
|
||||||
{ label: 'Sasak', value: 'sasak' },
|
// { label: 'Sasak', value: 'sasak' },
|
||||||
{ label: 'Papua', value: 'papua' },
|
// { label: 'Papua', value: 'papua' },
|
||||||
{ label: 'Lainnya', value: 'lainnya', priority: -100 },
|
// { label: 'Lainnya', value: 'lainnya', priority: -100 },
|
||||||
]
|
// ]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -70,7 +71,7 @@ const ethnicOptions = [
|
|||||||
<Combobox
|
<Combobox
|
||||||
:id="fieldName"
|
:id="fieldName"
|
||||||
v-bind="componentField"
|
v-bind="componentField"
|
||||||
:items="ethnicOptions"
|
:items="items"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:is-disabled="isDisabled"
|
:is-disabled="isDisabled"
|
||||||
search-placeholder="Cari..."
|
search-placeholder="Cari..."
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { cn } from '~/lib/utils'
|
|||||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
items: { value: string; label: string }[]
|
||||||
fieldName?: string
|
fieldName?: string
|
||||||
label?: string
|
label?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
@@ -29,15 +30,15 @@ const {
|
|||||||
labelClass,
|
labelClass,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const langOptions = [
|
// const langOptions = [
|
||||||
{ label: 'Bahasa Indonesia', value: 'id', priority: 1 },
|
// { label: 'Bahasa Indonesia', value: 'id', priority: 1 },
|
||||||
{ label: 'Bahasa Jawa', value: 'jawa' },
|
// { label: 'Bahasa Jawa', value: 'jawa' },
|
||||||
{ label: 'Bahasa Sunda', value: 'sunda' },
|
// { label: 'Bahasa Sunda', value: 'sunda' },
|
||||||
{ label: 'Bahasa Bali', value: 'bali' },
|
// { label: 'Bahasa Bali', value: 'bali' },
|
||||||
{ label: 'Bahasa Jaksel', value: 'jaksel' },
|
// { label: 'Bahasa Jaksel', value: 'jaksel' },
|
||||||
{ label: 'Bahasa Inggris', value: 'en' },
|
// { label: 'Bahasa Inggris', value: 'en' },
|
||||||
{ label: 'Tidak Diketahui', value: 'unknown', priority: 100 },
|
// { label: 'Tidak Diketahui', value: 'unknown', priority: 100 },
|
||||||
]
|
// ]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -64,7 +65,7 @@ const langOptions = [
|
|||||||
:is-disabled="isDisabled"
|
:is-disabled="isDisabled"
|
||||||
:id="fieldName"
|
:id="fieldName"
|
||||||
v-bind="componentField"
|
v-bind="componentField"
|
||||||
:items="langOptions"
|
:items="items"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:preserve-order="false"
|
:preserve-order="false"
|
||||||
:class="
|
:class="
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const props = defineProps<{
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'back'): void
|
(e: 'back'): void
|
||||||
(e: 'edit'): void
|
(e: 'edit'): void
|
||||||
|
(e: 'preview', url: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
@@ -70,6 +71,14 @@ const patientAge = computed(() => {
|
|||||||
}
|
}
|
||||||
return calculateAge(props.patient.person.birthDate)
|
return calculateAge(props.patient.person.birthDate)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const familiyCardFile = computed(() => {
|
||||||
|
return props.patient.person.familyIdentityFileUrl || ''
|
||||||
|
})
|
||||||
|
const identityFile = computed(() => {
|
||||||
|
return props.patient.person.residentIdentityFileUrl || ''
|
||||||
|
})
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Lifecycle Hooks
|
// #region Lifecycle Hooks
|
||||||
@@ -166,13 +175,85 @@ function onNavigate(type: ClickType) {
|
|||||||
<AccordionTrigger>{{ section }}</AccordionTrigger>
|
<AccordionTrigger>{{ section }}</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<!-- Dokumen KTP -->
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<span class="text-sm text-muted-foreground">Dokumen KTP</span>
|
<span class="text-sm text-muted-foreground">Dokumen KTP</span>
|
||||||
<Skeleton class="h-32 w-64 rounded-md" />
|
<div
|
||||||
|
class="relative h-32 w-64 cursor-pointer overflow-hidden rounded-md border bg-muted"
|
||||||
|
:class="{ 'cursor-not-allowed opacity-60': !identityFile }"
|
||||||
|
@click="identityFile && emit('preview', identityFile)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="identityFile"
|
||||||
|
:src="identityFile"
|
||||||
|
alt="Dokumen KTP"
|
||||||
|
class="h-full w-full object-cover"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
v-else
|
||||||
|
class="h-full w-full"
|
||||||
|
/>
|
||||||
|
<!-- Overlay -->
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 flex items-center justify-center bg-black/50 transition-opacity"
|
||||||
|
:class="identityFile ? 'opacity-0 hover:opacity-100' : 'opacity-100'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="identityFile"
|
||||||
|
class="flex flex-col items-center gap-1 text-white"
|
||||||
|
>
|
||||||
|
<span class="i-lucide-eye h-6 w-6" />
|
||||||
|
<span class="text-sm font-medium">Lihat Dokumen</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="flex flex-col items-center gap-1 text-white/70"
|
||||||
|
>
|
||||||
|
<span class="i-lucide-info h-6 w-6" />
|
||||||
|
<span class="text-sm font-medium">No Data</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Dokumen KK -->
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<span class="text-sm text-muted-foreground">Dokumen KK</span>
|
<span class="text-sm text-muted-foreground">Dokumen KK</span>
|
||||||
<Skeleton class="h-32 w-64 rounded-md" />
|
<div
|
||||||
|
class="relative h-32 w-64 cursor-pointer overflow-hidden rounded-md border bg-muted"
|
||||||
|
:class="{ 'cursor-not-allowed opacity-60': !familiyCardFile }"
|
||||||
|
@click="familiyCardFile && emit('preview', familiyCardFile)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="familiyCardFile"
|
||||||
|
:src="familiyCardFile"
|
||||||
|
alt="Dokumen KK"
|
||||||
|
class="h-full w-full object-cover"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
v-else
|
||||||
|
class="h-full w-full"
|
||||||
|
/>
|
||||||
|
<!-- Overlay -->
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 flex items-center justify-center bg-black/50 transition-opacity"
|
||||||
|
:class="familiyCardFile ? 'opacity-0 hover:opacity-100' : 'opacity-100'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="familiyCardFile"
|
||||||
|
class="flex flex-col items-center gap-1 text-white"
|
||||||
|
>
|
||||||
|
<span class="i-lucide-eye h-6 w-6" />
|
||||||
|
<span class="text-sm font-medium">Lihat Dokumen</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="flex flex-col items-center gap-1 text-white/70"
|
||||||
|
>
|
||||||
|
<span class="i-lucide-info h-6 w-6" />
|
||||||
|
<span class="text-sm font-medium">No Data</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
|
|||||||
@@ -0,0 +1,476 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { FormErrors } from '~/types/error'
|
||||||
|
import { Form } from '~/components/pub/ui/form'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import Block from '~/components/pub/my-ui/form/block.vue'
|
||||||
|
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
|
||||||
|
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
|
||||||
|
import Field from '~/components/pub/my-ui/form/field.vue'
|
||||||
|
import Label from '~/components/pub/my-ui/form/label.vue'
|
||||||
|
import { educationCodes, genderCodes, occupationCodes, religionCodes } from '~/lib/constants'
|
||||||
|
import { mapToComboboxOptList } from '~/lib/utils'
|
||||||
|
|
||||||
|
interface DivisionFormData {
|
||||||
|
name: string
|
||||||
|
code: string
|
||||||
|
parentId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
division: {
|
||||||
|
msg: {
|
||||||
|
placeholder: string
|
||||||
|
search: string
|
||||||
|
empty: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items: {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
code: string
|
||||||
|
}[]
|
||||||
|
schema: any
|
||||||
|
initialValues?: Partial<DivisionFormData>
|
||||||
|
errors?: FormErrors
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
submit: [values: DivisionFormData, resetForm: () => void]
|
||||||
|
cancel: [resetForm: () => void]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const educationOpts = mapToComboboxOptList(educationCodes)
|
||||||
|
const occupationOpts = mapToComboboxOptList(occupationCodes)
|
||||||
|
const religionOpts = mapToComboboxOptList(religionCodes)
|
||||||
|
const genderOpts = mapToComboboxOptList(genderCodes)
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(props.schema)
|
||||||
|
|
||||||
|
// Form submission handler
|
||||||
|
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||||
|
const formData: DivisionFormData = {
|
||||||
|
name: values.name || '',
|
||||||
|
code: values.code || '',
|
||||||
|
parentId: values.parentId || '',
|
||||||
|
}
|
||||||
|
emit('submit', formData, resetForm)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Form
|
||||||
|
v-slot="{ handleSubmit, resetForm }"
|
||||||
|
as=""
|
||||||
|
keep-values
|
||||||
|
:validation-schema="formSchema"
|
||||||
|
:initial-values="initialValues"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
id="entry-form"
|
||||||
|
@submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))"
|
||||||
|
>
|
||||||
|
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||||
|
<div class="flex flex-col justify-between">
|
||||||
|
<Block>
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="residentIdentityNumber">KTP</Label>
|
||||||
|
<Field
|
||||||
|
id="residentIdentityNumber"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="residentIdentityNumber"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
id="residentIdentityNumber"
|
||||||
|
type="text"
|
||||||
|
maxlength="16"
|
||||||
|
placeholder="Nomor KTP"
|
||||||
|
autocomplete="off"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<!-- FrontTitle -->
|
||||||
|
<FieldGroup :column="3">
|
||||||
|
<Label label-for="frontTitle">Gelar Depan</Label>
|
||||||
|
<Field
|
||||||
|
id="frontTitle"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="frontTitle"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
id="frontTitle"
|
||||||
|
type="text"
|
||||||
|
placeholder="Dr., Ir., dll"
|
||||||
|
autocomplete="off"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup :column="3">
|
||||||
|
<Label
|
||||||
|
label-for="name"
|
||||||
|
position="dynamic"
|
||||||
|
>
|
||||||
|
Nama
|
||||||
|
</Label>
|
||||||
|
<Field
|
||||||
|
id="name"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="name"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Nama lengkap"
|
||||||
|
autocomplete="off"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<!-- EndTitle -->
|
||||||
|
<FieldGroup :column="3">
|
||||||
|
<Label
|
||||||
|
label-for="endTitle"
|
||||||
|
position="dynamic"
|
||||||
|
>
|
||||||
|
Gelar Belakang
|
||||||
|
</Label>
|
||||||
|
<Field
|
||||||
|
id="endTitle"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="endTitle"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
id="endTitle"
|
||||||
|
type="text"
|
||||||
|
placeholder="S.Kom, M.Kes, dll"
|
||||||
|
autocomplete="off"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<!-- BirthDate -->
|
||||||
|
<FieldGroup :column="2">
|
||||||
|
<Label label-for="birthDate">Tanggal Lahir</Label>
|
||||||
|
<Field
|
||||||
|
id="birthDate"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="birthDate"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
id="birthDate"
|
||||||
|
type="date"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<!-- BirthRegency_Code -->
|
||||||
|
<FieldGroup :column="2">
|
||||||
|
<Label label-for="birthRegencyCode">Tempat Lahir</Label>
|
||||||
|
<Field
|
||||||
|
id="birthRegencyCode"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="birthRegencyCode"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="parentId"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="educationOpts"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<!-- Gender_Code -->
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="genderCode">Jenis Kelamin</Label>
|
||||||
|
<Field
|
||||||
|
id="genderCode"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="genderCode"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="genderCode"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="genderOpts"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<!-- PassportNumber -->
|
||||||
|
<FieldGroup :column="2">
|
||||||
|
<Label label-for="passportNumber">Paspor</Label>
|
||||||
|
<Field
|
||||||
|
id="passportNumber"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="passportNumber"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
id="passportNumber"
|
||||||
|
type="text"
|
||||||
|
placeholder="Nomor Paspor"
|
||||||
|
autocomplete="off"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<!-- DrivingLicenseNumber -->
|
||||||
|
<FieldGroup :column="2">
|
||||||
|
<Label label-for="drivingLicenseNumber">SIM</Label>
|
||||||
|
<Field
|
||||||
|
id="drivingLicenseNumber"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="drivingLicenseNumber"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
id="drivingLicenseNumber"
|
||||||
|
type="text"
|
||||||
|
placeholder="Nomor SIM"
|
||||||
|
autocomplete="off"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<!-- Religion_Code -->
|
||||||
|
<FieldGroup :column="2">
|
||||||
|
<Label label-for="religionCode">Agama</Label>
|
||||||
|
<Field
|
||||||
|
id="religionCode"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="religionCode"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="religionCode"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="religionOpts"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup :column="2">
|
||||||
|
<Label label-for="ethnicCode">Suku</Label>
|
||||||
|
<Field
|
||||||
|
id="ethnicCode"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="ethnicCode"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="ethnicCode"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="occupationOpts"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<!-- Language_Code -->
|
||||||
|
<FieldGroup :column="2">
|
||||||
|
<Label label-for="languageCode">Bahasa</Label>
|
||||||
|
<Field
|
||||||
|
id="languageCode"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="languageCode"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="parentId"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="educationOpts"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<!-- Education_Code -->
|
||||||
|
<FieldGroup :column="2">
|
||||||
|
<Label label-for="educationCode">Pendidikan</Label>
|
||||||
|
<Field
|
||||||
|
id="educationCode"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="educationCode"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="educationCode"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="educationOpts"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<!-- Occupation_Code -->
|
||||||
|
<FieldGroup :column="2">
|
||||||
|
<Label label-for="occupationCode">Pekerjaan</Label>
|
||||||
|
<Field
|
||||||
|
id="occupationCode"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="occupationCode"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="occupationCode"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="occupationOpts"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<!-- Occupation_Name -->
|
||||||
|
<FieldGroup :column="2">
|
||||||
|
<Label label-for="occupationName">Detail Pekerjaan</Label>
|
||||||
|
<Field
|
||||||
|
id="occupationName"
|
||||||
|
:errors="errors"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
name="occupationName"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
id="occupationName"
|
||||||
|
type="text"
|
||||||
|
placeholder="Contoh: Guru SMP, Petani"
|
||||||
|
autocomplete="off"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
</Block>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
@@ -15,13 +15,14 @@ import { useIntegrationSepEntry } from '~/handlers/integration-sep-entry.handler
|
|||||||
// Props
|
// Props
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
id: number
|
id: number
|
||||||
classCode?: 'ambulatory' | 'emergency' | 'inpatient' | 'outpatient'
|
classCode?: 'ambulatory' | 'emergency' | 'inpatient'
|
||||||
subclassCode?: 'reg' | 'rehab' | 'chemo' | 'emg' | 'eon' | 'op' | 'icu' | 'hcu' | 'vk'
|
subclassCode?: 'regular' | 'rehab' | 'chemo' | 'emg' | 'eon' | 'op' | 'icu' | 'hcu' | 'vk'
|
||||||
formType: string
|
formType: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const formRef = ref<InstanceType<typeof AppEncounterEntryForm> | null>(null)
|
const formRef = ref<InstanceType<typeof AppEncounterEntryForm> | null>(null)
|
||||||
|
// const patientMode = ref<'new' | 'exists'>('exists')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
paymentsList,
|
paymentsList,
|
||||||
@@ -56,7 +57,6 @@ const {
|
|||||||
getDoctorInfo,
|
getDoctorInfo,
|
||||||
getValidateMember,
|
getValidateMember,
|
||||||
getValidateSepNumber,
|
getValidateSepNumber,
|
||||||
handleFetchDoctors,
|
|
||||||
} = useEncounterEntry(props)
|
} = useEncounterEntry(props)
|
||||||
const { recSepId, openHistory, histories, getMonitoringHistoryMappers } = useIntegrationSepEntry()
|
const { recSepId, openHistory, histories, getMonitoringHistoryMappers } = useIntegrationSepEntry()
|
||||||
|
|
||||||
@@ -82,11 +82,13 @@ function handleFetch(value?: any) {
|
|||||||
|
|
||||||
async function handleEvent(menu: string, value?: any) {
|
async function handleEvent(menu: string, value?: any) {
|
||||||
if (menu === 'search') {
|
if (menu === 'search') {
|
||||||
|
// patientMode.value = 'exists'
|
||||||
getPatientsList({ 'page-size': 10, includes: 'person' }).then(() => {
|
getPatientsList({ 'page-size': 10, includes: 'person' }).then(() => {
|
||||||
openPatient.value = true
|
openPatient.value = true
|
||||||
})
|
})
|
||||||
} else if (menu === 'add') {
|
} else if (menu === 'add') {
|
||||||
navigateTo('/client/patient/add')
|
// navigateTo('/client/patient/add')
|
||||||
|
// patientMode.value = 'new'
|
||||||
} else if (menu === 'add-sep') {
|
} else if (menu === 'add-sep') {
|
||||||
if (isSepValid.value) {
|
if (isSepValid.value) {
|
||||||
return
|
return
|
||||||
@@ -167,6 +169,7 @@ onMounted(async () => {
|
|||||||
Kunjungan
|
Kunjungan
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- :patientMode="patientMode" -->
|
||||||
<AppEncounterEntryForm
|
<AppEncounterEntryForm
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:mode="props.formType"
|
:mode="props.formType"
|
||||||
@@ -226,10 +229,10 @@ onMounted(async () => {
|
|||||||
/>
|
/>
|
||||||
Batal
|
Batal
|
||||||
</Button>
|
</Button>
|
||||||
|
<!-- :disabled="isSaveDisabled" -->
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
class="h-[40px] min-w-[120px] text-white"
|
class="h-[40px] min-w-[120px] text-white"
|
||||||
:disabled="isSaveDisabled"
|
|
||||||
@click="handleSaveClick"
|
@click="handleSaveClick"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import FilterForm from '~/components/app/encounter/filter-form.vue'
|
|||||||
// Props
|
// Props
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
classCode?: 'ambulatory' | 'emergency' | 'inpatient'
|
classCode?: 'ambulatory' | 'emergency' | 'inpatient'
|
||||||
subclassCode?: 'reg' | 'rehab' | 'chemo' | 'emg' | 'eon' | 'op' | 'icu' | 'hcu' | 'vk'
|
subclassCode?: 'regular' | 'rehab' | 'chemo' | 'emg' | 'eon' | 'op' | 'icu' | 'hcu' | 'vk' | undefined
|
||||||
canCreate?: boolean
|
canCreate?: boolean
|
||||||
canUpdate?: boolean
|
canUpdate?: boolean
|
||||||
canDelete?: boolean
|
canDelete?: boolean
|
||||||
@@ -162,7 +162,6 @@ async function getPatientList() {
|
|||||||
'Responsible_Doctor-employee',
|
'Responsible_Doctor-employee',
|
||||||
'Responsible_Doctor-employee-person',
|
'Responsible_Doctor-employee-person',
|
||||||
'EncounterDocuments',
|
'EncounterDocuments',
|
||||||
'unit',
|
|
||||||
'vclaimReference', // vclaimReference | vclaimSep
|
'vclaimReference', // vclaimReference | vclaimSep
|
||||||
]
|
]
|
||||||
const includesParams = includesParamsArrays.join(',')
|
const includesParams = includesParamsArrays.join(',')
|
||||||
@@ -172,8 +171,11 @@ async function getPatientList() {
|
|||||||
if (props.classCode) {
|
if (props.classCode) {
|
||||||
params['class-code'] = props.classCode
|
params['class-code'] = props.classCode
|
||||||
}
|
}
|
||||||
if (props.subclassCode) {
|
if (props.subclassCode == 'regular') {
|
||||||
params['subclass-code'] = props.subclassCode
|
params['specialist-code'] = 'rehab'
|
||||||
|
params['specialist-code-opt'] = 'ne'
|
||||||
|
} else {
|
||||||
|
params['specialist-code'] = 'rehab'
|
||||||
}
|
}
|
||||||
const result = await getEncounterList(params)
|
const result = await getEncounterList(params)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -365,6 +367,7 @@ function handleRemoveConfirmation() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
{{ subclassCode }}
|
||||||
<CH.ContentHeader v-bind="hreaderPrep">
|
<CH.ContentHeader v-bind="hreaderPrep">
|
||||||
<FilterNav
|
<FilterNav
|
||||||
:active-positon="activeServicePosition"
|
:active-positon="activeServicePosition"
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import type { Person } from '~/models/person'
|
|||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||||
|
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||||
|
import DocPreviewDialog from '~/components/pub/my-ui/modal/doc-preview-dialog.vue'
|
||||||
|
|
||||||
import { getPatientDetail } from '~/services/patient.service'
|
import { getPatientDetail } from '~/services/patient.service'
|
||||||
|
|
||||||
@@ -17,6 +19,9 @@ const props = defineProps<{
|
|||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region State & Computed
|
// #region State & Computed
|
||||||
|
const isDocPreviewDialogOpen = ref<boolean>(false)
|
||||||
|
const docPreviewUrl = ref<string>('')
|
||||||
|
|
||||||
const patient = ref(
|
const patient = ref(
|
||||||
withBase<PatientEntity>({
|
withBase<PatientEntity>({
|
||||||
person: {} as Person,
|
person: {} as Person,
|
||||||
@@ -80,6 +85,19 @@ async function onEdit() {
|
|||||||
:patient="patient"
|
:patient="patient"
|
||||||
@back="onBack"
|
@back="onBack"
|
||||||
@edit="onEdit"
|
@edit="onEdit"
|
||||||
|
@preview="
|
||||||
|
(url: string) => {
|
||||||
|
docPreviewUrl = url
|
||||||
|
isDocPreviewDialogOpen = true
|
||||||
|
}
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
|
<Dialog
|
||||||
|
v-model:open="isDocPreviewDialogOpen"
|
||||||
|
title="Preview Dokumen"
|
||||||
|
size="2xl"
|
||||||
|
>
|
||||||
|
<DocPreviewDialog :link="docPreviewUrl" />
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -17,6 +17,8 @@ import AppPersonAddressEntryFormRelative from '~/components/app/person-address/e
|
|||||||
import AppPersonFamilyParentsForm from '~/components/app/person/family-parents-form.vue'
|
import AppPersonFamilyParentsForm from '~/components/app/person/family-parents-form.vue'
|
||||||
import AppPersonContactEntryForm from '~/components/app/person-contact/entry-form.vue'
|
import AppPersonContactEntryForm from '~/components/app/person-contact/entry-form.vue'
|
||||||
import AppPersonRelativeEntryForm from '~/components/app/person-relative/entry-form.vue'
|
import AppPersonRelativeEntryForm from '~/components/app/person-relative/entry-form.vue'
|
||||||
|
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||||
|
import DocPreviewDialog from '~/components/pub/my-ui/modal/doc-preview-dialog.vue'
|
||||||
|
|
||||||
// helper
|
// helper
|
||||||
import { format, parseISO } from 'date-fns'
|
import { format, parseISO } from 'date-fns'
|
||||||
@@ -24,6 +26,8 @@ import { id as localeID } from 'date-fns/locale'
|
|||||||
|
|
||||||
// services
|
// services
|
||||||
import { getPatientDetail, uploadAttachment } from '~/services/patient.service'
|
import { getPatientDetail, uploadAttachment } from '~/services/patient.service'
|
||||||
|
import { getValueLabelList as getEthnicOpts } from '~/services/ethnic.service'
|
||||||
|
import { getValueLabelList as getLanguageOpts } from '~/services/language.service'
|
||||||
|
|
||||||
import { isReadonly, isProcessing, handleActionSave, handleActionEdit } from '~/handlers/patient.handler'
|
import { isReadonly, isProcessing, handleActionSave, handleActionEdit } from '~/handlers/patient.handler'
|
||||||
|
|
||||||
@@ -47,6 +51,23 @@ const props = defineProps<{
|
|||||||
const residentIdentityFile = ref<File>()
|
const residentIdentityFile = ref<File>()
|
||||||
const familyCardFile = ref<File>()
|
const familyCardFile = ref<File>()
|
||||||
|
|
||||||
|
// Dialog preview dokumen
|
||||||
|
const isDocPreviewDialogOpen = ref<boolean>(false)
|
||||||
|
const docPreviewUrl = ref<string>('')
|
||||||
|
|
||||||
|
// Computed untuk URL dokumen tersimpan
|
||||||
|
const identityFileUrl = computed(() => {
|
||||||
|
return patientDetail.value.person?.residentIdentityFileUrl || ''
|
||||||
|
})
|
||||||
|
const familyCardFileUrl = computed(() => {
|
||||||
|
return patientDetail.value.person?.familyIdentityFileUrl || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
function handlePreviewDoc(url: string) {
|
||||||
|
docPreviewUrl.value = url
|
||||||
|
isDocPreviewDialogOpen.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// form related state
|
// form related state
|
||||||
const personAddressForm = ref<ExposedForm<any> | null>(null)
|
const personAddressForm = ref<ExposedForm<any> | null>(null)
|
||||||
const personAddressRelativeForm = ref<ExposedForm<any> | null>(null)
|
const personAddressRelativeForm = ref<ExposedForm<any> | null>(null)
|
||||||
@@ -58,6 +79,9 @@ const personPatientForm = ref<ExposedForm<any> | null>(null)
|
|||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region State & Computed
|
// #region State & Computed
|
||||||
|
const ethnicOptions = ref<{ value: string; label: string }[]>([])
|
||||||
|
const languageOptions = ref<{ value: string; label: string }[]>([])
|
||||||
|
|
||||||
const patientDetail = ref(
|
const patientDetail = ref(
|
||||||
withBase<PatientEntity>({
|
withBase<PatientEntity>({
|
||||||
person: {} as Person,
|
person: {} as Person,
|
||||||
@@ -223,6 +247,13 @@ const responsibleFormInitialValues = computed(() => {
|
|||||||
|
|
||||||
// #region Lifecycle Hooks
|
// #region Lifecycle Hooks
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
const optsReq = {
|
||||||
|
'page-no-limit': true,
|
||||||
|
}
|
||||||
|
|
||||||
|
ethnicOptions.value = await getEthnicOpts(optsReq, true)
|
||||||
|
languageOptions.value = await getLanguageOpts(optsReq, true)
|
||||||
|
|
||||||
// if edit mode, fetch patient detail
|
// if edit mode, fetch patient detail
|
||||||
if (props.mode === 'edit' && props.patientId) {
|
if (props.mode === 'edit' && props.patientId) {
|
||||||
await loadInitData(props.patientId)
|
await loadInitData(props.patientId)
|
||||||
@@ -309,7 +340,9 @@ async function handleActionClick(eventType: string) {
|
|||||||
const patient: Patient = await composeFormData()
|
const patient: Patient = await composeFormData()
|
||||||
|
|
||||||
let createdPatientId = 0
|
let createdPatientId = 0
|
||||||
|
let createdPersonId = 0
|
||||||
let response: any
|
let response: any
|
||||||
|
|
||||||
// return
|
// return
|
||||||
if (props.mode === 'edit' && props.patientId) {
|
if (props.mode === 'edit' && props.patientId) {
|
||||||
response = await handleActionEdit(
|
response = await handleActionEdit(
|
||||||
@@ -330,13 +363,15 @@ async function handleActionClick(eventType: string) {
|
|||||||
|
|
||||||
const data = (response?.body?.data ?? null) as PatientBase | null
|
const data = (response?.body?.data ?? null) as PatientBase | null
|
||||||
if (!data) return
|
if (!data) return
|
||||||
|
|
||||||
createdPatientId = data.id
|
createdPatientId = data.id
|
||||||
|
createdPersonId = data.person_id!
|
||||||
|
|
||||||
if (residentIdentityFile.value) {
|
if (residentIdentityFile.value) {
|
||||||
void uploadAttachment(residentIdentityFile.value, createdPatientId, 'ktp')
|
void uploadAttachment(residentIdentityFile.value, createdPersonId, 'ktp')
|
||||||
}
|
}
|
||||||
if (familyCardFile.value) {
|
if (familyCardFile.value) {
|
||||||
void uploadAttachment(familyCardFile.value, createdPatientId, 'kk')
|
void uploadAttachment(familyCardFile.value, createdPersonId, 'kk')
|
||||||
}
|
}
|
||||||
|
|
||||||
// If has callback provided redirect to callback with patientData
|
// If has callback provided redirect to callback with patientData
|
||||||
@@ -435,10 +470,16 @@ watch(
|
|||||||
{{ mode === 'edit' ? 'Edit Pasien' : 'Tambah Pasien' }}
|
{{ mode === 'edit' ? 'Edit Pasien' : 'Tambah Pasien' }}
|
||||||
</div>
|
</div>
|
||||||
<AppPatientEntryForm
|
<AppPatientEntryForm
|
||||||
:key="`patient-${formKey}`"
|
|
||||||
ref="personPatientForm"
|
ref="personPatientForm"
|
||||||
|
:key="`patient-${formKey}`"
|
||||||
:is-readonly="isProcessing || isReadonly"
|
:is-readonly="isProcessing || isReadonly"
|
||||||
:initial-values="patientFormInitialValues"
|
:initial-values="patientFormInitialValues"
|
||||||
|
:language-options="languageOptions"
|
||||||
|
:ethnic-options="ethnicOptions"
|
||||||
|
:mode="mode"
|
||||||
|
:identity-file-url="identityFileUrl"
|
||||||
|
:family-card-file-url="familyCardFileUrl"
|
||||||
|
@preview="handlePreviewDoc"
|
||||||
/>
|
/>
|
||||||
<div class="h-6"></div>
|
<div class="h-6"></div>
|
||||||
<AppPersonAddressEntryForm
|
<AppPersonAddressEntryForm
|
||||||
@@ -483,6 +524,15 @@ watch(
|
|||||||
<div class="my-2 flex justify-end py-2">
|
<div class="my-2 flex justify-end py-2">
|
||||||
<Action @click="handleActionClick" />
|
<Action @click="handleActionClick" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Dialog Preview Dokumen -->
|
||||||
|
<Dialog
|
||||||
|
v-model:open="isDocPreviewDialogOpen"
|
||||||
|
title="Preview Dokumen"
|
||||||
|
size="2xl"
|
||||||
|
>
|
||||||
|
<DocPreviewDialog :link="docPreviewUrl" />
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
import { getDetail as getDoctorDetail, getValueLabelList as getDoctorValueLabelList } from '~/services/doctor.service'
|
import { getDetail as getDoctorDetail, getValueLabelList as getDoctorValueLabelList } from '~/services/doctor.service'
|
||||||
import {
|
import {
|
||||||
create as createEncounter,
|
create as createEncounter,
|
||||||
|
createWithPatient,
|
||||||
getDetail as getEncounterDetail,
|
getDetail as getEncounterDetail,
|
||||||
update as updateEncounter,
|
update as updateEncounter,
|
||||||
} from '~/services/encounter.service'
|
} from '~/services/encounter.service'
|
||||||
@@ -44,7 +45,8 @@ import {
|
|||||||
export function useEncounterEntry(props: {
|
export function useEncounterEntry(props: {
|
||||||
id: number
|
id: number
|
||||||
classCode?: 'ambulatory' | 'emergency' | 'inpatient' | 'outpatient'
|
classCode?: 'ambulatory' | 'emergency' | 'inpatient' | 'outpatient'
|
||||||
subclassCode?: 'reg' | 'rehab' | 'chemo' | 'emg' | 'eon' | 'op' | 'icu' | 'hcu' | 'vk'
|
subclassCode?: 'regular' | 'rehab' | 'chemo' | 'emg' | 'eon' | 'op' | 'icu' | 'hcu' | 'vk'
|
||||||
|
specialist_Code?: 'regular' | 'rehab' | 'chemo' | 'emg' | 'eon' | 'op' | 'icu' | 'hcu' | 'vk'
|
||||||
}) {
|
}) {
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
@@ -207,7 +209,7 @@ export function useEncounterEntry(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getDoctorInfo(value: string) {
|
async function getDoctorInfo(value: string) {
|
||||||
const resp = await getDoctorDetail(value, { includes: 'unit,specialist,subspecialist' })
|
const resp = await getDoctorDetail(value, { includes: 'specialist,subspecialist' })
|
||||||
if (resp.success) {
|
if (resp.success) {
|
||||||
selectedDoctor.value = resp.body.data
|
selectedDoctor.value = resp.body.data
|
||||||
}
|
}
|
||||||
@@ -294,22 +296,22 @@ export function useEncounterEntry(props: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFetchDoctors(unit?: string, specialist_code?: string, subSpecialist_code?: string) {
|
async function handleFetchDoctors(specialist_code?: string, subSpecialist_code?: string) {
|
||||||
try {
|
try {
|
||||||
const filterParams: Record<string, any> = {
|
const filterParams: Record<string, any> = {
|
||||||
'page-size': 100,
|
'page-size': 100,
|
||||||
includes: 'employee-Person,unit,specialist,subspecialist'
|
includes: 'employee-Person,specialist,subspecialist'
|
||||||
}
|
}
|
||||||
|
|
||||||
// rehab is special
|
// rehab is special
|
||||||
if (unit) {
|
// if (unit) {
|
||||||
if (unit == 'rehab') {
|
// if (unit == 'rehab') {
|
||||||
filterParams['unit-code'] = 'rehab'
|
// filterParams['unit-code'] = 'rehab'
|
||||||
} else {
|
// } else {
|
||||||
filterParams['unit-code'] = 'rehab'
|
// filterParams['unit-code'] = 'rehab'
|
||||||
filterParams['unit-code-opt'] = 'ne'
|
// filterParams['unit-code-opt'] = 'ne'
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if (specialist_code) {
|
if (specialist_code) {
|
||||||
if (specialist_code == 'rehab') {
|
if (specialist_code == 'rehab') {
|
||||||
filterParams['specialist-code'] = 'rehab'
|
filterParams['specialist-code'] = 'rehab'
|
||||||
@@ -318,7 +320,9 @@ export function useEncounterEntry(props: {
|
|||||||
filterParams['specialist-code-opt'] = 'ne'
|
filterParams['specialist-code-opt'] = 'ne'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (subSpecialist_code) filterParams['subspecialist-code'] = subSpecialist_code
|
if (subSpecialist_code) {
|
||||||
|
filterParams['subspecialist-code'] = subSpecialist_code
|
||||||
|
}
|
||||||
doctorsList.value = await getDoctorValueLabelList(filterParams, true)
|
doctorsList.value = await getDoctorValueLabelList(filterParams, true)
|
||||||
|
|
||||||
// const isSub = getIsSubspecialist(subSpecialistId, specialistsTree.value)
|
// const isSub = getIsSubspecialist(subSpecialistId, specialistsTree.value)
|
||||||
@@ -507,13 +511,18 @@ export function useEncounterEntry(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSaveEncounter(formValues: any) {
|
async function handleSaveEncounter(formValues: any) {
|
||||||
if (!selectedPatient.value || !selectedPatientObject.value) {
|
let enccounterRef = formValues
|
||||||
toast({
|
if (!formValues.encounter) {
|
||||||
title: 'Gagal',
|
if (!selectedPatient.value || !selectedPatientObject.value) {
|
||||||
description: 'Pasien harus dipilih terlebih dahulu',
|
toast({
|
||||||
variant: 'destructive',
|
title: 'Gagal',
|
||||||
})
|
description: 'Pasien harus dipilih terlebih dahulu',
|
||||||
return
|
variant: 'destructive',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
enccounterRef = {...formValues.encounter}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -527,24 +536,24 @@ export function useEncounterEntry(props: {
|
|||||||
return date.toISOString()
|
return date.toISOString()
|
||||||
}
|
}
|
||||||
|
|
||||||
const { specialist_id, subspecialist_id } = getSpecialistIdsFromCode(formValues.subSpecialistId || '')
|
const { specialist_id, subspecialist_id } = getSpecialistIdsFromCode(enccounterRef.subSpecialistId || '')
|
||||||
|
|
||||||
const patientId = formValues.patient_id || selectedPatientObject.value?.id || Number(selectedPatient.value)
|
const patientId = enccounterRef.patient_id || selectedPatientObject.value?.id || Number(selectedPatient.value)
|
||||||
|
|
||||||
const registeredAtValue = formValues.registeredAt || formValues.registerDate || ''
|
const registeredAtValue = enccounterRef.registeredAt || enccounterRef.registerDate || ''
|
||||||
const visitDateValue = formValues.visitDate || formValues.registeredAt || formValues.registerDate || ''
|
const visitDateValue = enccounterRef.visitDate || enccounterRef.registeredAt || enccounterRef.registerDate || ''
|
||||||
const memberNumber = formValues.member_number ?? formValues.cardNumber ?? formValues.memberNumber ?? null
|
const memberNumber = enccounterRef.member_number ?? enccounterRef.cardNumber ?? enccounterRef.memberNumber ?? null
|
||||||
const refNumber = formValues.ref_number ?? formValues.sepNumber ?? formValues.refNumber ?? null
|
const refNumber = enccounterRef.ref_number ?? enccounterRef.sepNumber ?? enccounterRef.refNumber ?? null
|
||||||
sepFile.value = formValues.sepFile || null
|
sepFile.value = enccounterRef.sepFile || null
|
||||||
sippFile.value = formValues.sippFile || null
|
sippFile.value = enccounterRef.sippFile || null
|
||||||
|
|
||||||
let paymentMethodCode = formValues.paymentMethod_code ?? null
|
let paymentMethodCode = enccounterRef.paymentMethod_code ?? null
|
||||||
if (!isUsePaymentNew && !paymentMethodCode) {
|
if (!isUsePaymentNew && !paymentMethodCode) {
|
||||||
if (formValues.paymentType === 'jkn' || formValues.paymentType === 'jkmm') {
|
if (enccounterRef.paymentType === 'jkn' || enccounterRef.paymentType === 'jkmm') {
|
||||||
paymentMethodCode = 'insurance'
|
paymentMethodCode = 'insurance'
|
||||||
} else if (formValues.paymentType === 'spm') {
|
} else if (enccounterRef.paymentType === 'spm') {
|
||||||
paymentMethodCode = 'cash'
|
paymentMethodCode = 'cash'
|
||||||
} else if (formValues.paymentType === 'pks') {
|
} else if (enccounterRef.paymentType === 'pks') {
|
||||||
paymentMethodCode = 'membership'
|
paymentMethodCode = 'membership'
|
||||||
} else {
|
} else {
|
||||||
paymentMethodCode = 'cash'
|
paymentMethodCode = 'cash'
|
||||||
@@ -553,15 +562,16 @@ export function useEncounterEntry(props: {
|
|||||||
|
|
||||||
const payload: any = {
|
const payload: any = {
|
||||||
patient_id: patientId,
|
patient_id: patientId,
|
||||||
appointment_doctor_code: formValues.doctor_code || null,
|
appointment_doctor_code: enccounterRef.doctor_code || null,
|
||||||
class_code: props.classCode || '',
|
class_code: props.classCode || '',
|
||||||
subClass_code: props.subclassCode || '',
|
subClass_code: props.subclassCode || '',
|
||||||
infra_id: formValues.infra_id ?? null,
|
specialist_code: selectedDoctor.value?.specialist_code || '',
|
||||||
unit_code: formValues.unitCode ?? userStore?.user?.unit_code ?? null,
|
infra_id: enccounterRef.infra_id ?? null,
|
||||||
refSource_name: formValues.refSource_name ?? 'RSSA',
|
unit_code: enccounterRef.unitCode ?? userStore?.user?.unit_code ?? null,
|
||||||
refTypeCode: formValues.paymentType === 'jkn' ? 'bpjs' : '',
|
refSource_name: enccounterRef.refSource_name ?? 'RSSA',
|
||||||
|
refTypeCode: enccounterRef.paymentType === 'jkn' ? 'bpjs' : '',
|
||||||
vclaimReference: vclaimReference.value ?? null,
|
vclaimReference: vclaimReference.value ?? null,
|
||||||
paymentType: formValues.paymentType,
|
paymentType: enccounterRef.paymentType,
|
||||||
registeredAt: formatDate(registeredAtValue),
|
registeredAt: formatDate(registeredAtValue),
|
||||||
visitDate: formatDate(visitDateValue),
|
visitDate: formatDate(visitDateValue),
|
||||||
}
|
}
|
||||||
@@ -588,10 +598,10 @@ export function useEncounterEntry(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (paymentMethodCode === 'insurance') {
|
if (paymentMethodCode === 'insurance') {
|
||||||
payload.insuranceCompany_id = formValues.insuranceCompany_id ?? null
|
payload.insuranceCompany_id = enccounterRef.insuranceCompany_id ?? null
|
||||||
if (memberNumber) payload.member_number = memberNumber
|
if (memberNumber) payload.member_number = memberNumber
|
||||||
if (formValues.refTypeCode) payload.refTypeCode = formValues.refTypeCode
|
if (enccounterRef.refTypeCode) payload.refTypeCode = enccounterRef.refTypeCode
|
||||||
if (formValues.vclaimReference) payload.vclaimReference = formValues.vclaimReference
|
if (enccounterRef.vclaimReference) payload.vclaimReference = enccounterRef.vclaimReference
|
||||||
} else {
|
} else {
|
||||||
if (paymentMethodCode === 'membership' && memberNumber) {
|
if (paymentMethodCode === 'membership' && memberNumber) {
|
||||||
payload.member_number = memberNumber
|
payload.member_number = memberNumber
|
||||||
@@ -607,8 +617,18 @@ export function useEncounterEntry(props: {
|
|||||||
if (isEditMode.value) {
|
if (isEditMode.value) {
|
||||||
result = await updateEncounter(props.id, payload)
|
result = await updateEncounter(props.id, payload)
|
||||||
} else {
|
} else {
|
||||||
console.log('💾 [ADD MODE] Sending POST request:', { payload })
|
// console.log('💾 [ADD MODE] Sending POST request:', { payload })
|
||||||
result = await createEncounter(payload)
|
if (!formValues.encounter) {
|
||||||
|
result = await createEncounter(payload)
|
||||||
|
} else {
|
||||||
|
if (formValues.patient?.person?.birthDate) {
|
||||||
|
formValues.patient.person.birthDate += 'T00:00:00.000Z'
|
||||||
|
}
|
||||||
|
result = await createWithPatient({
|
||||||
|
encounter: payload,
|
||||||
|
patient: formValues.patient,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import type { EncounterDocument } from "./encounter-document"
|
|||||||
import type { InternalReference } from "./internal-reference"
|
import type { InternalReference } from "./internal-reference"
|
||||||
import type { Nurse } from "./nurse"
|
import type { Nurse } from "./nurse"
|
||||||
import { type Patient, genPatient } from "./patient"
|
import { type Patient, genPatient } from "./patient"
|
||||||
|
import type { Person } from "./person"
|
||||||
|
import type { PersonAddress } from "./person-address"
|
||||||
|
import type { PersonContact } from "./person-contact"
|
||||||
|
import type { PersonRelative } from "./person-relative"
|
||||||
import type { Specialist } from "./specialist"
|
import type { Specialist } from "./specialist"
|
||||||
import type { Subspecialist } from "./subspecialist"
|
import type { Subspecialist } from "./subspecialist"
|
||||||
import { genUnit, type Unit } from "./unit"
|
import { genUnit, type Unit } from "./unit"
|
||||||
@@ -45,6 +49,16 @@ export interface Encounter {
|
|||||||
encounterDocuments: EncounterDocument[]
|
encounterDocuments: EncounterDocument[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreateDtoWithPatient {
|
||||||
|
encounter: Encounter
|
||||||
|
patient: {
|
||||||
|
person: Person
|
||||||
|
personAddresses: PersonAddress[]
|
||||||
|
personContacts: PersonContact[]
|
||||||
|
personRelatives: PersonRelative[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function genEncounter(): Encounter {
|
export function genEncounter(): Encounter {
|
||||||
return {
|
return {
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -62,6 +76,7 @@ export function genEncounter(): Encounter {
|
|||||||
medicalDischargeEducation: '',
|
medicalDischargeEducation: '',
|
||||||
status_code: '',
|
status_code: '',
|
||||||
encounterDocuments: [],
|
encounterDocuments: [],
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type Base, genBase } from "./_base"
|
import { type Base, genBase } from './_base'
|
||||||
|
|
||||||
export interface Ethnic extends Base {
|
export interface Ethnic extends Base {
|
||||||
code: string
|
code: string
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { type Base, genBase } from "./_base"
|
import { type Base, genBase } from './_base'
|
||||||
|
|
||||||
export interface Language extends Base {
|
export interface Language extends Base {
|
||||||
code: string
|
code: string
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function genMcuSrc(): Language {
|
export function genLanguage(): Language {
|
||||||
return {
|
return {
|
||||||
...genBase(),
|
...genBase(),
|
||||||
code: '',
|
code: '',
|
||||||
|
|||||||
@@ -32,29 +32,26 @@ const hasAccess = getPageAccess(roleAccess, 'create')
|
|||||||
|
|
||||||
// TODO: Make a function for this
|
// TODO: Make a function for this
|
||||||
const { user } = useUserStore()
|
const { user } = useUserStore()
|
||||||
const servicePosition = user.user_contractPosition_code == 'emp' ? getServicePosition('emp|'+user.employee_position_code) : null
|
const servicePosition = user.user_contractPosition_code == 'emp' ? getServicePosition(user.activeRole) : null
|
||||||
const subClassCode = servicePosition == 'med' ?
|
const subClassCode = servicePosition == 'med' ?
|
||||||
// medic
|
// medic
|
||||||
(user.unit_code == 'rehab' ? 'rehab' : 'reg') :
|
(user.specialist_code == 'rehab' ? 'rehab' : 'regular') :
|
||||||
// non medic
|
// non medic
|
||||||
(
|
(
|
||||||
user.employee_position_code == 'reg' ?
|
servicePosition == 'reg' ?
|
||||||
(user.installation_code == 'rehab' ? 'rehab' : 'reg') :
|
(user.installation_code == 'rehab' ? 'rehab' : 'regular') :
|
||||||
undefined
|
undefined
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="hasAccess">
|
<Content v-if="hasAccess"
|
||||||
<Content
|
:id="0"
|
||||||
:id="0"
|
class-code="ambulatory"
|
||||||
class-code="ambulatory"
|
:subclass-code="subClassCode"
|
||||||
:subclass-code="subClassCode"
|
form-type="add"
|
||||||
form-type="add"
|
/>
|
||||||
/>
|
<Error v-else
|
||||||
</div>
|
|
||||||
<Error
|
|
||||||
v-else
|
|
||||||
:status-code="403"
|
:status-code="403"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -39,24 +39,23 @@ const canRemove = getPageAccess(roleAccess, 'delete')
|
|||||||
// User info
|
// User info
|
||||||
const { user } = useUserStore()
|
const { user } = useUserStore()
|
||||||
// TODO: Make a function for this
|
// TODO: Make a function for this
|
||||||
const servicePosition = user.user_contractPosition_code == 'emp' ? getServicePosition('emp|'+user.employee_position_code) : null
|
const servicePosition = user.user_contractPosition_code == 'emp' ? getServicePosition(user.activeRole) : null
|
||||||
const subClassCode = servicePosition == 'med' ?
|
const subClassCode = servicePosition == 'med' ?
|
||||||
// medic
|
// medic
|
||||||
(user.unit_code == 'rehab' ? 'rehab' : 'reg') :
|
(user.specialist_code == 'rehab' ? 'rehab' : 'regular') :
|
||||||
// non medic
|
// non medic
|
||||||
(
|
(
|
||||||
user.employee_position_code == 'reg' ?
|
servicePosition == 'reg' ?
|
||||||
(user.installation_code == 'rehab' ? 'rehab' : 'reg') :
|
(user.installation_code == 'rehab' ? 'rehab' : 'regular') :
|
||||||
undefined
|
undefined
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
{{ user.employee_position_code }}---<br />
|
|
||||||
{{ servicePosition }}---<br />
|
|
||||||
{{ subClassCode }}---
|
|
||||||
<div v-if="hasAccess">
|
<div v-if="hasAccess">
|
||||||
|
{{ servicePosition }}--
|
||||||
|
{{ subClassCode }}--
|
||||||
<Content
|
<Content
|
||||||
class-code="ambulatory"
|
class-code="ambulatory"
|
||||||
:subclass-code="subClassCode"
|
:subclass-code="subClassCode"
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { isValid } from "date-fns"
|
import { isValid } from "date-fns"
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { PatientSchema } from "./patient.schema"
|
||||||
|
import { PersonAddressSchema } from "./person-address.schema"
|
||||||
|
import { PersonContactBaseSchema } from "./person-contact.schema"
|
||||||
|
import { PersonAddressRelativeSchema } from "./person-address-relative.schema"
|
||||||
|
|
||||||
const ERROR_MESSAGES = {
|
const ERROR_MESSAGES = {
|
||||||
required: {
|
required: {
|
||||||
@@ -18,12 +22,15 @@ const ERROR_MESSAGES = {
|
|||||||
|
|
||||||
const ACCEPTED_UPLOAD_TYPES = ['image/jpeg', 'image/png', 'application/pdf']
|
const ACCEPTED_UPLOAD_TYPES = ['image/jpeg', 'image/png', 'application/pdf']
|
||||||
const isValidationSep = false
|
const isValidationSep = false
|
||||||
|
// const encounterPatientShcema = z.object({
|
||||||
|
// })
|
||||||
|
|
||||||
const IntegrationEncounterSchema = z
|
const IntegrationEncounterSchema = z
|
||||||
.object({
|
.object({
|
||||||
// Patient data (readonly, populated from selected patient)
|
// Patient data (readonly, populated from selected patient)
|
||||||
|
patientSource: z.string().optional(),
|
||||||
patientName: z.string().optional(),
|
patientName: z.string().optional(),
|
||||||
nationalIdentity: z.string().optional(),
|
residentIdentiyNumber: z.string().optional(),
|
||||||
medicalRecordNumber: z.string().optional(),
|
medicalRecordNumber: z.string().optional(),
|
||||||
|
|
||||||
// Visit data
|
// Visit data
|
||||||
@@ -129,8 +136,21 @@ const IntegrationEncounterSchema = z
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const IntegrationEncounterWPSchema = z
|
||||||
|
.object({
|
||||||
|
encounter: IntegrationEncounterSchema,
|
||||||
|
patient: z.object({
|
||||||
|
person: PatientSchema,
|
||||||
|
personAddresses: z.array(PersonAddressSchema),
|
||||||
|
personContacts: z.array(PersonContactBaseSchema),
|
||||||
|
personRelatives: z.array(PersonAddressRelativeSchema),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
type IntegrationEncounterFormData = z.infer<typeof IntegrationEncounterSchema>
|
type IntegrationEncounterFormData = z.infer<typeof IntegrationEncounterSchema>
|
||||||
|
type IntegrationEncounterWPFormData = z.infer<typeof IntegrationEncounterWPSchema>
|
||||||
|
|
||||||
export { IntegrationEncounterSchema }
|
export { IntegrationEncounterSchema, IntegrationEncounterWPSchema }
|
||||||
export type { IntegrationEncounterFormData }
|
export type { IntegrationEncounterFormData, IntegrationEncounterWPFormData }
|
||||||
|
|
||||||
@@ -102,7 +102,8 @@ const PatientSchema = z
|
|||||||
(data) => {
|
(data) => {
|
||||||
if (data.nationality === 'WNI') {
|
if (data.nationality === 'WNI') {
|
||||||
const nik = data.identityNumber?.trim()
|
const nik = data.identityNumber?.trim()
|
||||||
return !!nik && nik.length === 16 && /^\d+$/.test(nik)
|
if (!nik) return true
|
||||||
|
return nik && nik.length === 16 && /^\d+$/.test(nik)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -65,3 +65,16 @@ export async function checkIn(id: number, data: CheckInFormData) {
|
|||||||
throw new Error(`Failed to put ${name}`)
|
throw new Error(`Failed to put ${name}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createWithPatient(data: any) {
|
||||||
|
try {
|
||||||
|
const resp = await xfetch(path + '/create-with-patient', 'POST', data)
|
||||||
|
const result: any = {}
|
||||||
|
result.success = resp.success
|
||||||
|
result.body = (resp.body as Record<string, any>) || {}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error putting ${name}:`, error)
|
||||||
|
throw new Error(`Failed to put ${name}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Base
|
||||||
|
import * as base from './_crud-base'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import type { Ethnic } from '~/models/ethnic'
|
||||||
|
|
||||||
|
const path = '/api/v1/ethnic'
|
||||||
|
const name = 'ethnic'
|
||||||
|
|
||||||
|
export function create(data: any) {
|
||||||
|
return base.create(path, data, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getList(params: any = null) {
|
||||||
|
return base.getList(path, params, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDetail(id: number | string) {
|
||||||
|
return base.getDetail(path, id, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update(id: number | string, data: any) {
|
||||||
|
return base.update(path, id, data, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remove(id: number | string) {
|
||||||
|
return base.remove(path, id, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getValueLabelList(
|
||||||
|
params: any = null,
|
||||||
|
useCodeAsValue = false,
|
||||||
|
): Promise<{ value: string; label: string }[]> {
|
||||||
|
let data: { value: string; label: string }[] = []
|
||||||
|
const result = await getList(params)
|
||||||
|
if (result.success) {
|
||||||
|
const resultData = result.body?.data || []
|
||||||
|
data = resultData.map((item: Ethnic) => ({
|
||||||
|
value: useCodeAsValue ? item.code : item.id ? Number(item.id) : item.code,
|
||||||
|
label: item.name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Base
|
||||||
|
import * as base from './_crud-base'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import type { Language } from '~/models/language'
|
||||||
|
|
||||||
|
const path = '/api/v1/language'
|
||||||
|
const name = 'language'
|
||||||
|
|
||||||
|
export function create(data: any) {
|
||||||
|
return base.create(path, data, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getList(params: any = null) {
|
||||||
|
return base.getList(path, params, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDetail(id: number | string) {
|
||||||
|
return base.getDetail(path, id, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update(id: number | string, data: any) {
|
||||||
|
return base.update(path, id, data, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remove(id: number | string) {
|
||||||
|
return base.remove(path, id, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getValueLabelList(
|
||||||
|
params: any = null,
|
||||||
|
useCodeAsValue = false,
|
||||||
|
): Promise<{ value: string; label: string }[]> {
|
||||||
|
let data: { value: string; label: string }[] = []
|
||||||
|
const result = await getList(params)
|
||||||
|
if (result.success) {
|
||||||
|
const resultData = result.body?.data || []
|
||||||
|
data = resultData.map((item: Language) => ({
|
||||||
|
value: useCodeAsValue ? item.code : item.id ? Number(item.id) : item.code,
|
||||||
|
label: item.name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
@@ -101,7 +101,7 @@ export async function removePatient(id: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function uploadAttachment(file: File, userId: number, key: UploadCodeKey) {
|
export async function uploadAttachment(file: File, personId: number, key: UploadCodeKey) {
|
||||||
try {
|
try {
|
||||||
const resolvedKey = uploadCode[key]
|
const resolvedKey = uploadCode[key]
|
||||||
if (!resolvedKey) {
|
if (!resolvedKey) {
|
||||||
@@ -110,11 +110,14 @@ export async function uploadAttachment(file: File, userId: number, key: UploadCo
|
|||||||
|
|
||||||
// siapkan form-data body
|
// siapkan form-data body
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('code', resolvedKey)
|
|
||||||
formData.append('content', file)
|
formData.append('content', file)
|
||||||
|
formData.append('entityType_code', 'person')
|
||||||
|
formData.append('ref_id', String(personId))
|
||||||
|
// formData.append('upload_employee_id', String(userId))
|
||||||
|
formData.append('type_code', resolvedKey)
|
||||||
|
|
||||||
// kirim via xfetch
|
// kirim via xfetch
|
||||||
const resp = await xfetch(`${mainUrl}/${userId}/upload`, 'POST', formData)
|
const resp = await xfetch(`/api/v1/upload-file`, 'POST', formData)
|
||||||
|
|
||||||
// struktur hasil sama seperti patchPatient
|
// struktur hasil sama seperti patchPatient
|
||||||
const result: any = {}
|
const result: any = {}
|
||||||
|
|||||||
Generated
+8544
-6636
File diff suppressed because it is too large
Load diff
@@ -21,11 +21,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"title": "IGD",
|
|
||||||
"icon": "i-lucide-zap",
|
|
||||||
"link": "/emergency/encounter"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"title": "Rawat Inap",
|
"title": "Rawat Inap",
|
||||||
"icon": "i-lucide-building-2",
|
"icon": "i-lucide-building-2",
|
||||||
|
|||||||
Reference in New Issue
Block a user