6ad99d45f2
* fix(style): formatting inconsistencies across codebase - Remove trailing semicolons from TypeScript imports - Fix Vue template indentation and line breaks - Standardize component attribute formatting - Remove unnecessary empty lines - Reorder import statements for consistency * chore: update import path and add editorconfig Update SidebarNavLink import path to match new component structure and add standard editorconfig for consistent code formatting
233 lines
7.9 KiB
Vue
233 lines
7.9 KiB
Vue
<script setup lang="ts">
|
|
import type { DataTableLoader } from '~/components/pub/base/data-table/type'
|
|
import { useUrlSearchParams } from '@vueuse/core'
|
|
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
|
import { actions, headerPrep, refSearchNav, service, summaryData, tabs } from './const'
|
|
import { defaultQuery, querySchema, tabSwitcher } from './schema.query'
|
|
|
|
// State management
|
|
const data = ref([])
|
|
const recId = ref<number>(0)
|
|
const recAction = ref<string>('')
|
|
const recItem = ref<any>(null)
|
|
const isLoading = reactive<DataTableLoader>({
|
|
satusehatConn: true,
|
|
isTableLoading: false,
|
|
})
|
|
const queryParams = useUrlSearchParams('history', {
|
|
initialValue: defaultQuery,
|
|
removeFalsyValues: true,
|
|
})
|
|
const params = computed(() => {
|
|
const result = querySchema.safeParse(queryParams)
|
|
return result.data
|
|
})
|
|
// Pagination state
|
|
const pagination = ref({
|
|
total: 0,
|
|
page: 1,
|
|
limit: 10,
|
|
total_pages: 0,
|
|
has_next: false,
|
|
has_prev: false,
|
|
})
|
|
|
|
// API function to fetch data
|
|
async function fetchData() {
|
|
try {
|
|
isLoading.isTableLoading = true
|
|
data.value = []
|
|
const response = await xfetch('/api/v1/satusehat/list', 'POST', {
|
|
resource_type: params.value?.resource_type,
|
|
date_from: params.value?.date_from,
|
|
date_to: params.value?.date_to,
|
|
search: params.value?.q,
|
|
page: params.value?.page,
|
|
limit: params.value?.limit,
|
|
})
|
|
|
|
if (response.success) {
|
|
data.value = response.body.data
|
|
pagination.value = response.body.meta
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching data:', error)
|
|
data.value = []
|
|
} finally {
|
|
isLoading.isTableLoading = false
|
|
}
|
|
}
|
|
|
|
async function callSatuSehat() {
|
|
try {
|
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
service.status = 'connected'
|
|
// service.status = 'error'
|
|
service.sessionActive = true
|
|
// service.sessionActive = false
|
|
} finally {
|
|
isLoading.satusehatConn = false
|
|
}
|
|
}
|
|
|
|
// Initialize params from URL on mount
|
|
onMounted(async () => {
|
|
await callSatuSehat()
|
|
await fetchData()
|
|
})
|
|
|
|
// Watch for url param changed state and trigger refetch
|
|
watch(params, () => {
|
|
fetchData()
|
|
})
|
|
|
|
provide('rec_id', recId)
|
|
provide('rec_action', recAction)
|
|
provide('rec_item', recItem)
|
|
provide('table_data_loader', isLoading)
|
|
|
|
// Sync active tab filter with queryparams
|
|
// fallback and default to `all` filter
|
|
const activeTabFilter = computed({
|
|
get: () => {
|
|
const result = tabSwitcher.parse(queryParams.resource_type)
|
|
return result
|
|
},
|
|
set: (value) => {
|
|
queryParams.resource_type = value
|
|
queryParams.q = defaultQuery.q
|
|
queryParams.page = defaultQuery.page
|
|
queryParams.limit = defaultQuery.limit
|
|
queryParams.date_from = defaultQuery.date_from
|
|
queryParams.date_to = defaultQuery.date_to
|
|
},
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="rounded-md border p-4">
|
|
<Header :prep="headerPrep" :ref-search-nav="refSearchNav" />
|
|
<div class="my-4 flex flex-1 flex-col gap-3 md:gap-4">
|
|
<PubBaseServiceStatus v-bind="service" />
|
|
<AppSatusehatCardSummary :is-loading="isLoading.satusehatConn!" :summary-data="summaryData" />
|
|
</div>
|
|
<div class="rounded-md border p-4">
|
|
<h2 class="text-md font-semibold py-2">FHIR Resource</h2>
|
|
<Tabs v-model="activeTabFilter">
|
|
<div class="scrollbar-hide overflow-x-auto flex gap-2 justify-between">
|
|
<TabsList>
|
|
<TabsTrigger
|
|
v-for="tab in tabs" :key="tab.value" :value="tab.value"
|
|
class="flex-shrink-0 px-4 py-2 text-sm font-medium data-[state=active]:bg-green-600 data-[state=inactive]:bg-gray-100 data-[state=active]:text-white data-[state=inactive]:text-gray-700"
|
|
>
|
|
{{ tab.label }}
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
<div class="flex gap-2 items-center">
|
|
|
|
<!-- Search Input -->
|
|
|
|
<AppSatusehatPicker />
|
|
<div class="relative w-full max-w-sm">
|
|
|
|
<Dialog>
|
|
<DialogTrigger>
|
|
<Input type="text" placeholder="Cari pasien..." class="pl-3 h-9" />
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogHeader class="border-b-1">
|
|
<DialogTitle class="pb-2">Pencarian</DialogTitle>
|
|
</DialogHeader>
|
|
<Form>
|
|
<FormField v-slot="{ componentField }" name="username">
|
|
<FormItem>
|
|
<FormLabel>Pasien</FormLabel>
|
|
<FormControl>
|
|
<Input type="text" placeholder="nama pasien, id pasien" v-bind="componentField" />
|
|
</FormControl>
|
|
</FormItem>
|
|
</FormField>
|
|
<FormField v-slot="{ componentField }" name="status">
|
|
<FormItem>
|
|
<FormLabel>Status</FormLabel>
|
|
<FormControl>
|
|
<Select v-bind="componentField">
|
|
<SelectTrigger class="bg-white border border-gray-300">
|
|
<SelectValue class="text-gray-400" placeholder="-- select item" />
|
|
</SelectTrigger>
|
|
<SelectContent class="bg-white ">
|
|
<SelectItem value="0">
|
|
Gagal
|
|
</SelectItem>
|
|
<SelectItem value="1">
|
|
Pending
|
|
</SelectItem>
|
|
<SelectItem value="2">
|
|
Terkirim
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</FormControl>
|
|
</FormItem>
|
|
</FormField>
|
|
<FormField v-slot="{ componentField }" name="fhirId">
|
|
<FormItem>
|
|
<FormLabel>FHIR ID</FormLabel>
|
|
<FormControl>
|
|
<Input type="text" placeholder="fhir id" v-bind="componentField" />
|
|
</FormControl>
|
|
</FormItem>
|
|
</FormField>
|
|
</Form>
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline">
|
|
Reset
|
|
</Button>
|
|
<Button>
|
|
Apply
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<AppSatusehatButtonAction
|
|
v-for="action in actions" :key="action.value" :icon="action.icon"
|
|
:text="action.label"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4">
|
|
<TabsContent v-for="tab in tabs" :key="`content-${tab.value}`" :value="tab.value">
|
|
<div class="rounded-md border p-4">
|
|
<AppSatusehatList v-if="!isLoading.satusehatConn" :data="data" />
|
|
|
|
<!-- Pagination -->
|
|
<div
|
|
v-if="!isLoading.satusehatConn && !isLoading.isTableLoading && pagination.total > 0"
|
|
class="mt-4 flex justify-between items-center"
|
|
>
|
|
<div class="text-sm text-muted-foreground">
|
|
Menampilkan {{ ((pagination.page - 1) * pagination.limit) + 1 }} -
|
|
{{ Math.min(pagination.page * pagination.limit, pagination.total) }}
|
|
dari {{ pagination.total }} data
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<Button v-if="pagination.has_prev" variant="outline" size="sm" @click="queryParams.page--">
|
|
Sebelumnya
|
|
</Button>
|
|
<Button v-if="pagination.has_next" variant="outline" size="sm" @click="queryParams.page++">
|
|
Selanjutnya
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</TabsContent>
|
|
</div>
|
|
</Tabs>
|
|
</div>
|
|
</div>
|
|
</template>
|