Merge pull request #44 from dikstub-rssa/fe-sep-38
Feat: SEP List and Form
This commit is contained in:
No files matched your search
@@ -0,0 +1,282 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// helpers
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import { useForm } from 'vee-validate'
|
||||||
|
import * as z from 'zod'
|
||||||
|
// components
|
||||||
|
import { Button } from '~/components/pub/ui/button'
|
||||||
|
import { Input } from '~/components/pub/ui/input'
|
||||||
|
import { Label } from '~/components/pub/ui/label'
|
||||||
|
import { Select } from '~/components/pub/ui/select'
|
||||||
|
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||||
|
import { Textarea } from '~/components/pub/ui/textarea'
|
||||||
|
import DatepickerSingle from '~/components/pub/custom-ui/form/datepicker-single.vue'
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'event', value: any): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ value: 'item-1', label: 'Item 1' },
|
||||||
|
{ value: 'item-2', label: 'Item 2' },
|
||||||
|
{ value: 'item-3', label: 'Item 3' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// Validation schema
|
||||||
|
const schema = z.object({
|
||||||
|
tanggalSep: z.string().min(1, 'Tanggal SEP wajib diisi'),
|
||||||
|
jalur: z.string().min(1, 'Pilih jalur'),
|
||||||
|
noBpjs: z.string().min(1, 'No. Kartu BPJS wajib diisi'),
|
||||||
|
noKtp: z.string().min(1, 'No. KTP wajib diisi'),
|
||||||
|
noRm: z.string().min(1, 'No. RM wajib diisi'),
|
||||||
|
namaPasien: z.string().min(1, 'Nama pasien wajib diisi'),
|
||||||
|
noTelp: z.string().min(1, 'Nomor telepon wajib diisi'),
|
||||||
|
noSuratKontrol: z.string().min(1, 'No. Surat Kontrol wajib diisi'),
|
||||||
|
tglSuratKontrol: z.string().min(1, 'Tanggal Surat Kontrol wajib diisi'),
|
||||||
|
klinikTujuan: z.string().min(1, 'Klinik tujuan wajib diisi'),
|
||||||
|
dpjp: z.string().min(1, 'DPJP wajib diisi'),
|
||||||
|
diagnosaAwal: z.string().min(1, 'Diagnosa awal wajib diisi'),
|
||||||
|
cob: z.string().min(1, 'COB wajib diisi'),
|
||||||
|
katarak: z.string().min(1, 'Katarak wajib diisi'),
|
||||||
|
jenisProsedur: z.string().min(1, 'Jenis prosedur wajib diisi'),
|
||||||
|
kodePenunjang: z.string().min(1, 'Kode penunjang wajib diisi'),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { handleSubmit, errors, defineField } = useForm({
|
||||||
|
validationSchema: toTypedSchema(schema),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Bind fields
|
||||||
|
const [tanggalSep] = defineField('tanggalSep')
|
||||||
|
const [jalur] = defineField('jalur')
|
||||||
|
const [noBpjs] = defineField('noBpjs')
|
||||||
|
const [noKtp] = defineField('noKtp')
|
||||||
|
const [noRm] = defineField('noRm')
|
||||||
|
const [namaPasien] = defineField('namaPasien')
|
||||||
|
const [noTelp] = defineField('noTelp')
|
||||||
|
const [noSuratKontrol] = defineField('noSuratKontrol')
|
||||||
|
const [tglSuratKontrol] = defineField('tglSuratKontrol')
|
||||||
|
const [klinikTujuan] = defineField('klinikTujuan')
|
||||||
|
const [dpjp] = defineField('dpjp')
|
||||||
|
const [diagnosaAwal] = defineField('diagnosaAwal')
|
||||||
|
const [cob] = defineField('cob')
|
||||||
|
const [katarak] = defineField('katarak')
|
||||||
|
const [jenisProsedur] = defineField('jenisProsedur')
|
||||||
|
const [kodePenunjang] = defineField('kodePenunjang')
|
||||||
|
|
||||||
|
// Submit handler
|
||||||
|
const onSubmit = handleSubmit((values) => {
|
||||||
|
console.log('✅ Validated form values:', values)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mx-auto w-full">
|
||||||
|
<form @submit.prevent="onSubmit" class="grid gap-6 p-4">
|
||||||
|
<!-- Tanggal SEP & Jalur -->
|
||||||
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label for="tanggalSep">Tanggal SEP<span class="text-red-500">*</span></Label>
|
||||||
|
<DatepickerSingle v-model="tanggalSep" placeholder="Pilih tanggal sep" />
|
||||||
|
<p v-if="errors.tanggalSep" class="text-sm text-red-500">{{ errors.tanggalSep }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label for="jalur">Jalur</Label>
|
||||||
|
<Select icon-name="i-lucide-chevron-down" v-model="jalur" :items="items" placeholder="Pilih jalur"></Select>
|
||||||
|
<p v-if="errors.jalur" class="text-sm text-red-500">{{ errors.jalur }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<!-- Data Pasien -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<h3 class="text-lg font-semibold">Data Pasien</h3>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
class="h-[40px] rounded-md border-green-600 text-green-600 hover:bg-green-50"
|
||||||
|
@click="emit('event', 'search-patient')"
|
||||||
|
>
|
||||||
|
<Icon name="i-lucide-search" class="h-5 w-5" />
|
||||||
|
Cari Pasien
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label>No. Kartu BPJS<span class="text-red-500">*</span></Label>
|
||||||
|
<Input v-model="noBpjs" />
|
||||||
|
<p v-if="errors.noBpjs" class="text-sm text-red-500">{{ errors.noBpjs }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label>No. KTP<span class="text-red-500">*</span></Label>
|
||||||
|
<Input v-model="noKtp" />
|
||||||
|
<p v-if="errors.noKtp" class="text-sm text-red-500">{{ errors.noKtp }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label>No. RM<span class="text-red-500">*</span></Label>
|
||||||
|
<Input v-model="noRm" />
|
||||||
|
<p v-if="errors.noRm" class="text-sm text-red-500">{{ errors.noRm }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label>Nama Pasien<span class="text-red-500">*</span></Label>
|
||||||
|
<Input v-model="namaPasien" />
|
||||||
|
<p v-if="errors.namaPasien" class="text-sm text-red-500">{{ errors.namaPasien }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label>No. Telepon<span class="text-red-500">*</span></Label>
|
||||||
|
<Input v-model="noTelp" />
|
||||||
|
<p v-if="errors.noTelp" class="text-sm text-red-500">{{ errors.noTelp }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<!-- Data SEP -->
|
||||||
|
<h3 class="text-lg font-semibold">Data SEP</h3>
|
||||||
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label>No. Surat Kontrol<span class="text-red-500">*</span></Label>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Input class="flex-1" v-model="noSuratKontrol" />
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
class="h-[40px] rounded-md border-green-600 text-green-600 hover:bg-green-50"
|
||||||
|
@click="emit('event', 'search-letter')"
|
||||||
|
>
|
||||||
|
<Icon name="i-lucide-search" class="h-5 w-5" />
|
||||||
|
Cari Data
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p v-if="errors.noSuratKontrol" class="text-sm text-red-500">{{ errors.noSuratKontrol }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label>Tanggal Surat Kontrol<span class="text-red-500">*</span></Label>
|
||||||
|
<DatepickerSingle v-model="tglSuratKontrol" placeholder="Pilih tanggal surat kontrol" />
|
||||||
|
<p v-if="errors.tglSuratKontrol" class="text-sm text-red-500">{{ errors.tglSuratKontrol }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<div class="flex-1">
|
||||||
|
<Label>Klinik Tujuan<span class="text-red-500">*</span></Label>
|
||||||
|
<Select
|
||||||
|
icon-name="i-lucide-chevron-down"
|
||||||
|
v-model="klinikTujuan"
|
||||||
|
:items="items"
|
||||||
|
placeholder="Pilih klinik"
|
||||||
|
></Select>
|
||||||
|
<p v-if="errors.klinikTujuan" class="text-sm text-red-500">{{ errors.klinikTujuan }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label class="mb-2 block">Klinik Eksekutif<span class="text-red-500">*</span></Label>
|
||||||
|
<RadioGroup v-model="cob" class="flex items-center gap-2">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="Ya" id="cob-ya" />
|
||||||
|
<Label for="cob-ya">Ya</Label>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="Tidak" id="cob-tidak" />
|
||||||
|
<Label for="cob-tidak">Tidak</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label>DPJP<span class="text-red-500">*</span></Label>
|
||||||
|
<Select icon-name="i-lucide-chevron-down" v-model="dpjp" :items="items" placeholder="Pilih DPJP"></Select>
|
||||||
|
<p v-if="errors.dpjp" class="text-sm text-red-500">{{ errors.dpjp }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label>Diagnosa Awal<span class="text-red-500">*</span></Label>
|
||||||
|
<Input v-model="diagnosaAwal" />
|
||||||
|
<p v-if="errors.diagnosaAwal" class="text-sm text-red-500">{{ errors.diagnosaAwal }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Catatan -->
|
||||||
|
<div>
|
||||||
|
<Label>Catatan</Label>
|
||||||
|
<Textarea class="h-[200px] w-full border-gray-400 bg-white" placeholder="Masukkan catatan opsional" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div>
|
||||||
|
<Label class="mb-2 block">COB<span class="text-red-500">*</span></Label>
|
||||||
|
<RadioGroup v-model="cob" class="flex items-center gap-2">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="Ya" id="cob-ya" />
|
||||||
|
<Label for="cob-ya">Ya</Label>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="Tidak" id="cob-tidak" />
|
||||||
|
<Label for="cob-tidak">Tidak</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label class="mb-2 block">Katarak<span class="text-red-500">*</span></Label>
|
||||||
|
<RadioGroup v-model="katarak" class="flex items-center gap-2">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="Ya" id="katarak-ya" />
|
||||||
|
<Label for="katarak-ya">Ya</Label>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="Tidak" id="katarak-tidak" />
|
||||||
|
<Label for="katarak-tidak">Tidak</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-4 md:grid-cols-3">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label class="mb-2 block">Jenis Prosedur<span class="text-red-500">*</span></Label>
|
||||||
|
<RadioGroup v-model="jenisProsedur" class="flex items-center gap-2">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="procedure-one" id="procedure-one" />
|
||||||
|
<Label for="procedure-one">Prosedur tidak berkelanjutan</Label>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="procedure-two" id="procedure-two" />
|
||||||
|
<Label for="procedure-two">Prosedur dan terapi berkelanjutan</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-4 md:grid-cols-3">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label>Kode Penunjang<span class="text-red-500">*</span></Label>
|
||||||
|
<Select
|
||||||
|
icon-name="i-lucide-chevron-down"
|
||||||
|
v-model="kodePenunjang"
|
||||||
|
:items="items"
|
||||||
|
placeholder="Pilih Kode Penunjang"
|
||||||
|
></Select>
|
||||||
|
<p v-if="errors.kodePenunjang" class="text-sm text-red-500">{{ errors.kodePenunjang }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="mt-6 flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
class="h-[40px] min-w-[120px] text-green-600 hover:bg-green-50"
|
||||||
|
@click="emit('event', 'history-sep')"
|
||||||
|
>
|
||||||
|
<Icon name="i-lucide-history" class="h-5 w-5" /> Riwayat SEP
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
class="h-[40px] min-w-[120px] rounded-md border-green-600 text-green-600 hover:bg-green-50 hover:text-green-600"
|
||||||
|
>
|
||||||
|
<Icon name="i-lucide-eye" class="h-5 w-5" />Preview
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" class="h-[40px] min-w-[120px] text-white">
|
||||||
|
<Icon name="i-lucide-save" class="h-5 w-5" />
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import type { Col, KeyLabel, RecComponent, RecStrFuncComponent, Th } from '~/components/pub/custom-ui/data/types'
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
|
||||||
|
type SepDto = any
|
||||||
|
|
||||||
|
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
|
||||||
|
|
||||||
|
export const cols: Col[] = [
|
||||||
|
{ width: 120 }, // TGL. SEP
|
||||||
|
{ width: 150 }, // NO. SEP
|
||||||
|
{ width: 120 }, // PELAYANAN
|
||||||
|
{ width: 100 }, // JALUR
|
||||||
|
{ width: 150 }, // NO. RM
|
||||||
|
{ width: 200 }, // NAMA PASIEN
|
||||||
|
{ width: 150 }, // NO. KARTU BPJS
|
||||||
|
{ width: 150 }, // NO. SURAT KONTROL
|
||||||
|
{ width: 150 }, // TGL SURAT KONTROL
|
||||||
|
{ width: 150 }, // KLINIK TUJUAN
|
||||||
|
{ width: 200 }, // DPJP
|
||||||
|
{ width: 200 }, // DIAGNOSIS AWAL
|
||||||
|
{ width: 100 }, // AKSI
|
||||||
|
]
|
||||||
|
|
||||||
|
export const header: Th[][] = [
|
||||||
|
[
|
||||||
|
{ label: 'TGL. SEP' },
|
||||||
|
{ label: 'NO. SEP' },
|
||||||
|
{ label: 'PELAYANAN' },
|
||||||
|
{ label: 'JALUR' },
|
||||||
|
{ label: 'NO. RM' },
|
||||||
|
{ label: 'NAMA PASIEN' },
|
||||||
|
{ label: 'NO. KARTU BPJS' },
|
||||||
|
{ label: 'NO. SURAT KONTROL' },
|
||||||
|
{ label: 'TGL SURAT KONTROL' },
|
||||||
|
{ label: 'KLINIK TUJUAN' },
|
||||||
|
{ label: 'DPJP' },
|
||||||
|
{ label: 'DIAGNOSIS AWAL' },
|
||||||
|
{ label: 'AKSI' },
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
export const keys = [
|
||||||
|
'tgl_sep',
|
||||||
|
'no_sep',
|
||||||
|
'pelayanan',
|
||||||
|
'jalur',
|
||||||
|
'no_rm',
|
||||||
|
'nama_pasien',
|
||||||
|
'no_kartu_bpjs',
|
||||||
|
'no_surat_kontrol',
|
||||||
|
'tgl_surat_kontrol',
|
||||||
|
'klinik_tujuan',
|
||||||
|
'dpjp',
|
||||||
|
'diagnosis_awal',
|
||||||
|
'action',
|
||||||
|
]
|
||||||
|
|
||||||
|
export const delKeyNames: KeyLabel[] = [
|
||||||
|
{ key: 'no_sep', label: 'NO. SEP' },
|
||||||
|
{ key: 'nama_pasien', label: 'Nama Pasien' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const funcParsed: Record<string, (row: any, ...args: any[]) => any> = {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const funcComponent: RecStrFuncComponent = {
|
||||||
|
action(rec, idx) {
|
||||||
|
const res: RecComponent = {
|
||||||
|
idx,
|
||||||
|
rec: rec as object,
|
||||||
|
component: action,
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const funcHtml: Record<string, (row: any, ...args: any[]) => any> = {}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cols, header, keys, funcParsed, funcComponent, funcHtml } from './list-cfg'
|
||||||
|
|
||||||
|
interface SepData {
|
||||||
|
tgl_sep: string
|
||||||
|
no_sep: string
|
||||||
|
pelayanan: string
|
||||||
|
jalur: string
|
||||||
|
no_rm: string
|
||||||
|
nama_pasien: string
|
||||||
|
no_kartu_bpjs: string
|
||||||
|
no_surat_kontrol: string
|
||||||
|
tgl_surat_kontrol: string
|
||||||
|
klinik_tujuan: string
|
||||||
|
dpjp: string
|
||||||
|
diagnosis_awal: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
data: SepData[]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PubBaseDataTable
|
||||||
|
:cols="cols"
|
||||||
|
:header="header"
|
||||||
|
:keys="keys"
|
||||||
|
:rows="props.data"
|
||||||
|
:funcParsed="funcParsed"
|
||||||
|
:funcComponent="funcComponent"
|
||||||
|
:funcHtml="funcHtml"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogFooter,
|
||||||
|
} from '~/components/pub/ui/dialog'
|
||||||
|
import { Input } from '~/components/pub/ui/input'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean
|
||||||
|
histories: Array<{
|
||||||
|
no_sep: string
|
||||||
|
tgl_sep: string
|
||||||
|
no_rujukan: string
|
||||||
|
diagnosis: string
|
||||||
|
pelayanan: string
|
||||||
|
kelas: string
|
||||||
|
}>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:open', value: boolean): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const search = ref('')
|
||||||
|
|
||||||
|
const filteredHistories = computed(() => {
|
||||||
|
const histories = props.histories || []
|
||||||
|
return histories.filter((p) => p.no_sep.includes(search.value))
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Dialog :open="props.open" @update:open="emit('update:open', $event)">
|
||||||
|
<DialogTrigger as-child></DialogTrigger>
|
||||||
|
<DialogContent class="max-w-[50%]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>History SEP</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<!-- Input Search -->
|
||||||
|
<div class="mb-2 max-w-[50%]">
|
||||||
|
<Input v-model="search" placeholder="Cari berdasarkan No. SEP" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table -->
|
||||||
|
<div class="overflow-x-auto rounded-lg border">
|
||||||
|
<table class="w-full text-sm">
|
||||||
|
<thead class="bg-gray-100">
|
||||||
|
<tr class="text-left">
|
||||||
|
<th class="p-2">NO. SEP</th>
|
||||||
|
<th class="p-2">TGL. SEP</th>
|
||||||
|
<th class="p-2">NO. RUJUKAN</th>
|
||||||
|
<th class="p-2">DIAGNOSIS AWAL</th>
|
||||||
|
<th class="p-2">JENIS PELAYANAN</th>
|
||||||
|
<th class="p-2">KELAS RAWAT</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="font-normal">
|
||||||
|
<tr v-for="p in filteredHistories" :key="p.no_sep" class="border-t hover:bg-gray-50">
|
||||||
|
<td class="p-2">{{ p.no_sep }}</td>
|
||||||
|
<td class="p-2">{{ p.tgl_sep }}</td>
|
||||||
|
<td class="p-2">{{ p.no_rujukan }}</td>
|
||||||
|
<td class="p-2">{{ p.diagnosis }}</td>
|
||||||
|
<td class="p-2">{{ p.pelayanan }}</td>
|
||||||
|
<td class="p-2">{{ p.kelas }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<DialogFooter>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogFooter,
|
||||||
|
} from '~/components/pub/ui/dialog'
|
||||||
|
import { Button } from '~/components/pub/ui/button'
|
||||||
|
import { Input } from '~/components/pub/ui/input'
|
||||||
|
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean
|
||||||
|
letters: Array<{
|
||||||
|
noSurat: string
|
||||||
|
tglRencana: string
|
||||||
|
noSep: string
|
||||||
|
namaPasien: string
|
||||||
|
noBpjs: string
|
||||||
|
klinik: string
|
||||||
|
dokter: string
|
||||||
|
}>
|
||||||
|
selected: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:open', value: boolean): void
|
||||||
|
(e: 'update:selected', value: string): void
|
||||||
|
(e: 'save'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const search = ref('')
|
||||||
|
|
||||||
|
const filteredLetters = computed(() => {
|
||||||
|
const letters = props.letters || []
|
||||||
|
return letters.filter((p) => p.noSurat.includes(search.value) || p.noSep.includes(search.value))
|
||||||
|
})
|
||||||
|
|
||||||
|
function saveSelection() {
|
||||||
|
emit('save')
|
||||||
|
emit('update:open', false)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Dialog :open="props.open" @update:open="emit('update:open', $event)">
|
||||||
|
<DialogTrigger as-child></DialogTrigger>
|
||||||
|
<DialogContent class="max-w-[50%]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Cari No. Surat Kontrol</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<!-- Input Search -->
|
||||||
|
<div class="mb-2 max-w-[50%]">
|
||||||
|
<Input v-model="search" placeholder="Cari berdasarkan No. Surat Kontrol / No. SEP" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table -->
|
||||||
|
<div class="overflow-x-auto rounded-lg border">
|
||||||
|
<table class="w-full text-sm">
|
||||||
|
<thead class="bg-gray-100">
|
||||||
|
<tr class="text-left">
|
||||||
|
<th class="p-2"></th>
|
||||||
|
<th class="p-2">NO. SURAT KONTROL</th>
|
||||||
|
<th class="p-2">TGL RENCANA KONTROL</th>
|
||||||
|
<th class="p-2">NO. SEP</th>
|
||||||
|
<th class="p-2">NAMA PASIEN</th>
|
||||||
|
<th class="p-2">NO. KARTU BPJS</th>
|
||||||
|
<th class="p-2">KLINIK</th>
|
||||||
|
<th class="p-2">DOKTER</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="font-normal">
|
||||||
|
<tr v-for="p in filteredLetters" :key="p.noSurat" class="border-t hover:bg-gray-50">
|
||||||
|
<td class="p-2">
|
||||||
|
<RadioGroup :model-value="props.selected" @update:model-value="emit('update:selected', $event)">
|
||||||
|
<RadioGroupItem :id="p.noSurat" :value="p.noSurat" />
|
||||||
|
</RadioGroup>
|
||||||
|
</td>
|
||||||
|
<td class="p-2">{{ p.noSurat }}</td>
|
||||||
|
<td class="p-2">{{ p.tglRencana }}</td>
|
||||||
|
<td class="p-2">{{ p.noSep }}</td>
|
||||||
|
<td class="p-2">{{ p.namaPasien }}</td>
|
||||||
|
<td class="p-2">{{ p.noBpjs }}</td>
|
||||||
|
<td class="p-2">{{ p.klinik }}</td>
|
||||||
|
<td class="p-2">{{ p.dokter }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="default" class="h-[40px] min-w-[120px] text-white" @click="saveSelection">
|
||||||
|
<Icon name="i-lucide-save" class="h-5 w-5" />
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogFooter,
|
||||||
|
} from '~/components/pub/ui/dialog'
|
||||||
|
import { Button } from '~/components/pub/ui/button'
|
||||||
|
import { Input } from '~/components/pub/ui/input'
|
||||||
|
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean
|
||||||
|
patients: Array<{
|
||||||
|
ktp: string
|
||||||
|
rm: string
|
||||||
|
bpjs: string
|
||||||
|
nama: string
|
||||||
|
}>
|
||||||
|
selected: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:open', value: boolean): void
|
||||||
|
(e: 'update:selected', value: string): void
|
||||||
|
(e: 'save'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const search = ref('')
|
||||||
|
const filteredPatients = computed(() => {
|
||||||
|
const patients = props.patients || []
|
||||||
|
return patients.filter(
|
||||||
|
(p) =>
|
||||||
|
p.ktp.includes(search.value) ||
|
||||||
|
p.rm.includes(search.value) ||
|
||||||
|
p.bpjs.includes(search.value) ||
|
||||||
|
p.nama.toLowerCase().includes(search.value.toLowerCase()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
function saveSelection() {
|
||||||
|
emit('save')
|
||||||
|
emit('update:open', false)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Dialog :open="props.open" @update:open="emit('update:open', $event)">
|
||||||
|
<DialogTrigger as-child></DialogTrigger>
|
||||||
|
<DialogContent class="max-w-3xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Cari Pasien</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<!-- Input Search -->
|
||||||
|
<div class="mb-2 max-w-[50%]">
|
||||||
|
<Input v-model="search" placeholder="Cari berdasarkan No. KTP / No. RM / Nomor Kartu" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table -->
|
||||||
|
<div class="overflow-x-auto rounded-lg border">
|
||||||
|
<table class="w-full text-sm">
|
||||||
|
<thead class="bg-gray-100">
|
||||||
|
<tr class="text-left">
|
||||||
|
<th class="p-2"></th>
|
||||||
|
<th class="p-2">NO. KTP</th>
|
||||||
|
<th class="p-2">NO. RM</th>
|
||||||
|
<th class="p-2">NO. KARTU BPJS</th>
|
||||||
|
<th class="p-2">NAMA PASIEN</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="font-normal">
|
||||||
|
<tr v-for="p in filteredPatients" :key="p.ktp" class="border-t hover:bg-gray-50">
|
||||||
|
<td class="p-2">
|
||||||
|
<RadioGroup :model-value="props.selected" @update:model-value="emit('update:selected', $event)">
|
||||||
|
<RadioGroupItem :id="p.ktp" :value="p.ktp" />
|
||||||
|
</RadioGroup>
|
||||||
|
</td>
|
||||||
|
<td class="p-2">{{ p.ktp }}</td>
|
||||||
|
<td class="p-2">{{ p.rm }}</td>
|
||||||
|
<td class="p-2">{{ p.bpjs }}</td>
|
||||||
|
<td class="p-2">{{ p.nama }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="default" class="h-[40px] min-w-[120px] text-white" @click="saveSelection">
|
||||||
|
<Icon name="i-lucide-save" class="h-5 w-5" />
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const openPatient = ref(false)
|
||||||
|
const openLetter = ref(false)
|
||||||
|
const openHistory = ref(false)
|
||||||
|
const selectedPatient = ref('3456512345678880')
|
||||||
|
const selectedLetter = ref('SK22334442')
|
||||||
|
|
||||||
|
const patients = [
|
||||||
|
{
|
||||||
|
ktp: '3456512345678880',
|
||||||
|
rm: 'RM23311224',
|
||||||
|
bpjs: '334423213214',
|
||||||
|
nama: 'Ahmad Baidowi',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ktp: '345678804565123',
|
||||||
|
rm: 'RM23455667',
|
||||||
|
bpjs: '33442367656',
|
||||||
|
nama: 'Bian Maulana',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const letters = [
|
||||||
|
{
|
||||||
|
noSurat: 'SK22334442',
|
||||||
|
tglRencana: '12 Agustus 2025',
|
||||||
|
noSep: 'SEP3232332',
|
||||||
|
namaPasien: 'Ahmad Baidowi',
|
||||||
|
noBpjs: '33442331214',
|
||||||
|
klinik: 'Penyakit Dalam',
|
||||||
|
dokter: 'dr. Andi Prasetyo, Sp.PD-KHOM',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
noSurat: 'SK99120039',
|
||||||
|
tglRencana: '12 Agustus 2025',
|
||||||
|
noSep: 'SEP4443232',
|
||||||
|
namaPasien: 'Bian Maulana',
|
||||||
|
noBpjs: '33442367656',
|
||||||
|
klinik: 'Gigi',
|
||||||
|
dokter: 'dr. Achmad Suparjo',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const histories = [
|
||||||
|
{
|
||||||
|
no_sep: "SP23311224",
|
||||||
|
tgl_sep: "12 Agustus 2025",
|
||||||
|
no_rujukan: "123444",
|
||||||
|
diagnosis: "C34.9 – Karsinoma Paru",
|
||||||
|
pelayanan: "Rawat Jalan",
|
||||||
|
kelas: "Kelas II",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
no_sep: "SP23455667",
|
||||||
|
tgl_sep: "11 Agustus 2025",
|
||||||
|
no_rujukan: "2331221",
|
||||||
|
diagnosis: "K35 – Apendisitis akut",
|
||||||
|
pelayanan: "Rawat Jalan",
|
||||||
|
kelas: "Kelas II",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function handleSavePatient() {
|
||||||
|
console.log('Pasien dipilih:', selectedPatient.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSaveLetter() {
|
||||||
|
console.log('Letter dipilih:', selectedLetter.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEvent(value: string) {
|
||||||
|
if (value === 'search-patient') {
|
||||||
|
openPatient.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (value === 'search-letter') {
|
||||||
|
openLetter.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (value === 'history-sep') {
|
||||||
|
openHistory.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (value === 'back') {
|
||||||
|
navigateTo('/bpjs/sep')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||||
|
<Icon name="i-lucide-panel-bottom" class="me-2" />
|
||||||
|
<span class="font-semibold">Tambah</span> SEP
|
||||||
|
</div>
|
||||||
|
<AppSepEntryForm @event="handleEvent" />
|
||||||
|
<AppSepTableSearchPatient
|
||||||
|
v-model:open="openPatient"
|
||||||
|
v-model:selected="selectedPatient"
|
||||||
|
:patients="patients"
|
||||||
|
@save="handleSavePatient"
|
||||||
|
/>
|
||||||
|
<AppSepTableSearchLetter
|
||||||
|
v-model:open="openLetter"
|
||||||
|
v-model:selected="selectedLetter"
|
||||||
|
:letters="letters"
|
||||||
|
@save="handleSaveLetter"
|
||||||
|
/>
|
||||||
|
<AppSepTableHistorySep
|
||||||
|
v-model:open="openHistory"
|
||||||
|
:histories="histories"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DataTableLoader } from '~/components/pub/base/data-table/type'
|
||||||
|
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
|
||||||
|
import type { PaginationMeta } from '~/components/pub/custom-ui/pagination/pagination.type'
|
||||||
|
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
} from '~/components/pub/ui/dropdown-menu'
|
||||||
|
import { X, Check } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const search = ref('')
|
||||||
|
const dateRange = ref('12 Agustus 2025 - 32 Agustus 2025')
|
||||||
|
const open = ref(false)
|
||||||
|
|
||||||
|
const sepData = {
|
||||||
|
no_sep: 'SP23311224',
|
||||||
|
kartu: '001234',
|
||||||
|
nama: 'Kenzie',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SepData {
|
||||||
|
tgl_sep: string
|
||||||
|
no_sep: string
|
||||||
|
pelayanan: string
|
||||||
|
jalur: string
|
||||||
|
no_rm: string
|
||||||
|
nama_pasien: string
|
||||||
|
no_kartu_bpjs: string
|
||||||
|
no_surat_kontrol: string
|
||||||
|
tgl_surat_kontrol: string
|
||||||
|
klinik_tujuan: string
|
||||||
|
dpjp: string
|
||||||
|
diagnosis_awal: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const paginationMeta = reactive<PaginationMeta>({
|
||||||
|
recordCount: 0,
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
totalPage: 5,
|
||||||
|
hasNext: false,
|
||||||
|
hasPrev: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
console.log('pageChange', page)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = ref<SepData[]>([])
|
||||||
|
|
||||||
|
// contoh data dummy
|
||||||
|
const rows = [
|
||||||
|
{
|
||||||
|
tgl_sep: '12 Agustus 2025',
|
||||||
|
no_sep: 'SP23311224',
|
||||||
|
pelayanan: 'Rawat Jalan',
|
||||||
|
jalur: 'Kontrol',
|
||||||
|
no_rm: 'RM23311224',
|
||||||
|
nama_pasien: 'Ahmad Baidowi',
|
||||||
|
no_kartu_bpjs: '334423231214',
|
||||||
|
no_surat_kontrol: 'SK22334442',
|
||||||
|
tgl_surat_kontrol: '13 Agustus 2024',
|
||||||
|
klinik_tujuan: 'Penyakit dalam',
|
||||||
|
dpjp: 'dr. Andi Prasetyo, Sp.PD-KHOM',
|
||||||
|
diagnosis_awal: 'C34.9 - Karsinoma Paru',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tgl_sep: '12 Agustus 2025',
|
||||||
|
no_sep: 'SP23311224',
|
||||||
|
pelayanan: 'Rawat Jalan',
|
||||||
|
jalur: 'Kontrol',
|
||||||
|
no_rm: 'RM001234',
|
||||||
|
nama_pasien: 'Kenzie',
|
||||||
|
no_kartu_bpjs: '12301234',
|
||||||
|
no_surat_kontrol: '123456',
|
||||||
|
tgl_surat_kontrol: '10 Agustus 2024',
|
||||||
|
klinik_tujuan: 'Penyakit dalam',
|
||||||
|
dpjp: 'Dr. Andreas Sutaji',
|
||||||
|
diagnosis_awal: 'Bronchitis',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tgl_sep: '11 Agustus 2025',
|
||||||
|
no_sep: 'SP23455667',
|
||||||
|
pelayanan: 'Rawat Jalan',
|
||||||
|
jalur: 'Kontrol',
|
||||||
|
no_rm: 'RM001009',
|
||||||
|
nama_pasien: 'Abraham Sulaiman',
|
||||||
|
no_kartu_bpjs: '334235',
|
||||||
|
no_surat_kontrol: '123334',
|
||||||
|
tgl_surat_kontrol: '11 Agustus 2024',
|
||||||
|
klinik_tujuan: 'Penyakit dalam',
|
||||||
|
dpjp: 'Dr. Andreas Sutaji',
|
||||||
|
diagnosis_awal: 'Paru-paru basah',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const refSearchNav: RefSearchNav = {
|
||||||
|
onClick: () => {
|
||||||
|
// open filter modal
|
||||||
|
},
|
||||||
|
onInput: (_val: string) => {
|
||||||
|
// filter patient list
|
||||||
|
},
|
||||||
|
onClear: () => {
|
||||||
|
// clear url param
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoading = reactive<DataTableLoader>({
|
||||||
|
isTableLoading: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const recId = ref<number>(0)
|
||||||
|
const recAction = ref<string>('')
|
||||||
|
const recItem = ref<any>(null)
|
||||||
|
|
||||||
|
const headerPrep: HeaderPrep = {
|
||||||
|
title: 'Daftar SEP Prosedur',
|
||||||
|
icon: 'i-lucide-panel-bottom',
|
||||||
|
addNav: {
|
||||||
|
label: 'Tambah',
|
||||||
|
onClick: () => {
|
||||||
|
navigateTo('/bpjs/sep/add')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSepList() {
|
||||||
|
isLoading.dataListLoading = true
|
||||||
|
data.value = [...rows]
|
||||||
|
isLoading.dataListLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportCsv() {
|
||||||
|
console.log('Ekspor CSV dipilih')
|
||||||
|
// tambahkan logic untuk generate CSV
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportExcel() {
|
||||||
|
console.log('Ekspor Excel dipilih')
|
||||||
|
// tambahkan logic untuk generate Excel
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete() {
|
||||||
|
console.log('Data dihapus:', sepData)
|
||||||
|
open.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => recAction.value,
|
||||||
|
() => {
|
||||||
|
if (recAction.value === 'showConfirmDel') {
|
||||||
|
open.value = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getSepList()
|
||||||
|
})
|
||||||
|
|
||||||
|
provide('rec_id', recId)
|
||||||
|
provide('rec_action', recAction)
|
||||||
|
provide('rec_item', recItem)
|
||||||
|
provide('table_data_loader', isLoading)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="rounded-md border p-4">
|
||||||
|
<Header :prep="{ ...headerPrep }" />
|
||||||
|
<!-- Filter Bar -->
|
||||||
|
<div class="my-2 flex flex-wrap items-center gap-2">
|
||||||
|
<!-- Search -->
|
||||||
|
<Input v-model="search" placeholder="Cari No. SEP / No. Kartu BPJS..." class="w-72" />
|
||||||
|
|
||||||
|
<!-- Date Range -->
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button variant="outline" class="h-[40px] w-72 border-gray-400 bg-white text-right font-normal">
|
||||||
|
{{ dateRange }}
|
||||||
|
<Icon name="i-lucide-calendar" class="h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="p-2">
|
||||||
|
<Calendar mode="range" />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<!-- Export -->
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger as-child>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
class="ml-auto h-[40px] w-[120px] rounded-md border-green-600 text-green-600 hover:bg-green-50"
|
||||||
|
>
|
||||||
|
<Icon name="i-lucide-download" class="h-5 w-5" /> Ekspor
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent class="w-40">
|
||||||
|
<DropdownMenuItem @click="exportCsv"> Ekspor CSV </DropdownMenuItem>
|
||||||
|
<DropdownMenuItem @click="exportExcel"> Ekspor Excel </DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-md border p-4">
|
||||||
|
<AppSepList v-if="!isLoading.dataListLoading" :data="data" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<template v-if="paginationMeta">
|
||||||
|
<div v-if="paginationMeta.totalPage > 1">
|
||||||
|
<PubCustomUiPagination :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Trigger button -->
|
||||||
|
<Dialog v-model:open="open">
|
||||||
|
<DialogTrigger as-child></DialogTrigger>
|
||||||
|
|
||||||
|
<DialogContent class="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Hapus SEP</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<DialogDescription class="text-gray-700">
|
||||||
|
Apakah anda yakin ingin menghapus SEP dengan data:
|
||||||
|
</DialogDescription>
|
||||||
|
|
||||||
|
<div class="mt-4 space-y-2 text-sm">
|
||||||
|
<p>No. SEP : {{ sepData.no_sep }}</p>
|
||||||
|
<p>No. Kartu BPJS : {{ sepData.kartu }}</p>
|
||||||
|
<p>Nama Pasien : {{ sepData.nama }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter class="mt-6 flex justify-end gap-3">
|
||||||
|
<Button variant="outline" class="border-green-600 text-green-600 hover:bg-green-50" @click="open = false">
|
||||||
|
<X class="mr-1 h-4 w-4" /> Tidak
|
||||||
|
</Button>
|
||||||
|
<Button variant="destructive" class="bg-red-600 hover:bg-red-700" @click="handleDelete">
|
||||||
|
<Check class="mr-1 h-4 w-4" /> Ya
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// helpers
|
||||||
|
import { format, parseISO } from 'date-fns'
|
||||||
|
// components
|
||||||
|
import { Button } from '~/components/pub/ui/button'
|
||||||
|
import { Calendar } from '~/components/pub/ui/calendar'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '~/components/pub/ui/popover'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
placeholder?: string
|
||||||
|
modelValue?: Date | string | undefined
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: Date | string | undefined]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const date = ref<Date | any>(undefined)
|
||||||
|
|
||||||
|
watch(date, (value) => {
|
||||||
|
const newValue = format(value, 'yyyy-MM-dd')
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.modelValue) {
|
||||||
|
const value = props.modelValue
|
||||||
|
if (value instanceof Date) {
|
||||||
|
date.value = value
|
||||||
|
} else if (typeof value === 'string' && value) {
|
||||||
|
date.value = parseISO(value)
|
||||||
|
} else {
|
||||||
|
date.value = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col space-y-2">
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button variant="outline" class="bg-white border-gray-400 font-normal text-right h-[40px] w-full">
|
||||||
|
<div class="flex justify-between items-center w-full">
|
||||||
|
<p v-if="date">{{ format(date, 'PPP') }}</p>
|
||||||
|
<p v-else class="text-sm text-black text-opacity-50">{{ props.placeholder || 'Tanggal' }}</p>
|
||||||
|
<Icon name="i-lucide-calendar" class="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-0">
|
||||||
|
<Calendar v-model="date" mode="single" />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -28,7 +28,7 @@ const iconName = computed(() => props.iconName || 'i-radix-icons-caret-sort')
|
|||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
<SelectIcon as-child class="absolute right-3 top-1/2 -translate-y-1/2">
|
<SelectIcon as-child class="absolute right-3 top-1/2 -translate-y-1/2">
|
||||||
<Icon name="i-radix-icons-caret-sort" class="h-4 w-4 opacity-50" />
|
<Icon :name="iconName" class="h-4 w-4 opacity-50" />
|
||||||
</SelectIcon>
|
</SelectIcon>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</template>
|
</template>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { PagePermission } from '~/models/role'
|
||||||
|
import Error from '~/components/pub/base/error/error.vue'
|
||||||
|
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: [],
|
||||||
|
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
||||||
|
title: 'Tambah SEP',
|
||||||
|
contentFrame: 'cf-full-width',
|
||||||
|
})
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
title: () => route.meta.title as string,
|
||||||
|
})
|
||||||
|
|
||||||
|
const roleAccess: PagePermission = PAGE_PERMISSIONS['/doctor']
|
||||||
|
|
||||||
|
const { checkRole, hasCreateAccess } = useRBAC()
|
||||||
|
|
||||||
|
// Check if user has access to this page
|
||||||
|
const hasAccess = checkRole(roleAccess)
|
||||||
|
if (!hasAccess) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 403,
|
||||||
|
statusMessage: 'Access denied',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define permission-based computed properties
|
||||||
|
const canCreate = true // hasCreateAccess(roleAccess)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="canCreate">
|
||||||
|
<ContentSepEntry />
|
||||||
|
</div>
|
||||||
|
<Error v-else :status-code="403" />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { PagePermission } from '~/models/role'
|
||||||
|
import Error from '~/components/pub/base/error/error.vue'
|
||||||
|
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: ['rbac'],
|
||||||
|
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
||||||
|
title: 'Daftar User',
|
||||||
|
contentFrame: 'cf-full-width',
|
||||||
|
})
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
title: () => route.meta.title as string,
|
||||||
|
})
|
||||||
|
|
||||||
|
const roleAccess: PagePermission = PAGE_PERMISSIONS['/doctor']
|
||||||
|
|
||||||
|
const { checkRole, hasReadAccess } = useRBAC()
|
||||||
|
|
||||||
|
// Check if user has access to this page
|
||||||
|
const hasAccess = checkRole(roleAccess)
|
||||||
|
if (!hasAccess) {
|
||||||
|
navigateTo('/403')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define permission-based computed properties
|
||||||
|
const canRead = true // hasReadAccess(roleAccess)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="canRead">
|
||||||
|
<ContentSepList />
|
||||||
|
</div>
|
||||||
|
<Error v-else :status-code="403" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"@radix-icons/vue": "^1.0.0",
|
"@radix-icons/vue": "^1.0.0",
|
||||||
"@unovis/ts": "^1.5.1",
|
"@unovis/ts": "^1.5.1",
|
||||||
"@unovis/vue": "^1.5.1",
|
"@unovis/vue": "^1.5.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"embla-carousel": "^8.5.2",
|
"embla-carousel": "^8.5.2",
|
||||||
"embla-carousel-vue": "^8.5.2",
|
"embla-carousel-vue": "^8.5.2",
|
||||||
"h3": "^1.15.4",
|
"h3": "^1.15.4",
|
||||||
|
|||||||
Generated
+7
@@ -23,6 +23,9 @@ dependencies:
|
|||||||
'@unovis/vue':
|
'@unovis/vue':
|
||||||
specifier: ^1.5.1
|
specifier: ^1.5.1
|
||||||
version: 1.5.2(@unovis/[email protected])([email protected])
|
version: 1.5.2(@unovis/[email protected])([email protected])
|
||||||
|
date-fns:
|
||||||
|
specifier: ^4.1.0
|
||||||
|
version: 4.1.0
|
||||||
embla-carousel:
|
embla-carousel:
|
||||||
specifier: ^8.5.2
|
specifier: ^8.5.2
|
||||||
version: 8.6.0
|
version: 8.6.0
|
||||||
@@ -6124,6 +6127,10 @@ packages:
|
|||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/[email protected]:
|
||||||
|
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/[email protected]:
|
/[email protected]:
|
||||||
resolution: {integrity: sha512-xzWNQ6jk/+NtdfLyXEipbX55dmDSeteLFt/ayF+wZUU5bzKgmrDOxmInUTbyVRp46YwnJdkDA1KhB7WIXFofJw==}
|
resolution: {integrity: sha512-xzWNQ6jk/+NtdfLyXEipbX55dmDSeteLFt/ayF+wZUU5bzKgmrDOxmInUTbyVRp46YwnJdkDA1KhB7WIXFofJw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user