-
- {{ props.prep.title }}
+
+
+
+
+ {{ props.prep.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Minimal {{ props.prep.refSearchNav.minLength || 3 }} karakter untuk mencari
+
+
+
+
+
+
+
+
+ {{ props.prep.addNav.label }}
+
+
+
+
+
+
+
+ {{ props.prep.filterNav.label }}
+
+
+
+
+
+
+
+ {{ props.prep.printNav.label }}
+
+
diff --git a/app/components/pub/custom-ui/pagination/pagination.type.ts b/app/components/pub/custom-ui/pagination/pagination.type.ts
new file mode 100644
index 00000000..5c9d97f5
--- /dev/null
+++ b/app/components/pub/custom-ui/pagination/pagination.type.ts
@@ -0,0 +1,13 @@
+export interface PaginationMeta {
+ recordCount: number
+ // page : current pointer for viewing data
+ page: number
+ // pageSize: limit each page request
+ pageSize: number
+ // totalPage: recourdCount / pageSize
+ totalPage: number
+ // hasNext: check if there is next page
+ hasNext: boolean
+ // hasPrev: check if there is previous page
+ hasPrev: boolean
+}
diff --git a/app/components/pub/custom-ui/pagination/pagination.vue b/app/components/pub/custom-ui/pagination/pagination.vue
new file mode 100644
index 00000000..694e3b7b
--- /dev/null
+++ b/app/components/pub/custom-ui/pagination/pagination.vue
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+ Menampilkan {{ startRecord }}
+ hingga {{ endRecord }}
+ dari {{ formattedRecordCount }} data
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.value }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/pub/ui/select/Select.vue b/app/components/pub/ui/select/Select.vue
index dfde6ef8..37720694 100644
--- a/app/components/pub/ui/select/Select.vue
+++ b/app/components/pub/ui/select/Select.vue
@@ -1,15 +1,14 @@
+
+
+
+
+
+
+
+```
+
+## API Reference
+
+### Methods
+
+- `setFromZodError(zodError)` - Set errors dari ZodError
+- `setErrors(errors)` - Set errors manual
+- `setError(field, message)` - Set error untuk field tertentu
+- `clearError(field)` - Hapus error untuk field tertentu
+- `clearErrors()` - Hapus semua errors
+- `hasError(field)` - Cek apakah ada error untuk field
+- `getError(field)` - Ambil error message untuk field
+
+### Computed Properties
+
+- `hasErrors` - Boolean apakah ada error
+- `errorMessages` - Array semua error messages
+- `firstError` - Error pertama (untuk alert general)
+
+## Field Component
+
+Field component akan otomatis menampilkan error jika:
+1. Field memiliki `id` prop yang sesuai dengan field name
+2. Field menerima `errors` prop
+3. Ada error untuk field tersebut di dalam errors object
+
+Error akan ditampilkan dengan class `.field-error-info` yang sudah di-style dengan warna merah.
diff --git a/app/composables/useFormErrors.ts b/app/composables/useFormErrors.ts
new file mode 100644
index 00000000..2907540f
--- /dev/null
+++ b/app/composables/useFormErrors.ts
@@ -0,0 +1,107 @@
+import type { ZodError } from 'zod'
+import type { FormErrors } from '~/types/error'
+
+/**
+ * Composable untuk menangani form validation errors seperti Laravel
+ * Mengkonversi ZodError menjadi format yang mudah digunakan di template
+ */
+export function useFormErrors() {
+ const errors = ref
({})
+
+ /**
+ * Set errors dari ZodError
+ */
+ function setFromZodError(zodError: ZodError) {
+ const newErrors: FormErrors = {}
+
+ zodError.errors.forEach((error) => {
+ const field = error.path.join('.')
+ newErrors[field] = {
+ message: error.message,
+ code: error.code,
+ path: error.path,
+ }
+ })
+
+ errors.value = newErrors
+ }
+
+ /**
+ * Set errors manual (untuk error dari API response)
+ */
+ function setErrors(newErrors: FormErrors) {
+ errors.value = newErrors
+ }
+
+ /**
+ * Set error untuk field tertentu
+ */
+ function setError(field: string, message: string, extra: Record = {}) {
+ errors.value[field] = {
+ message,
+ ...extra,
+ }
+ }
+
+ /**
+ * Hapus error untuk field tertentu
+ */
+ function clearError(field: string) {
+ delete errors.value[field]
+ }
+
+ /**
+ * Hapus semua errors
+ */
+ function clearErrors() {
+ errors.value = {}
+ }
+
+ /**
+ * Cek apakah ada error untuk field tertentu
+ */
+ function hasError(field: string): boolean {
+ return !!errors.value[field]
+ }
+
+ /**
+ * Ambil error message untuk field tertentu
+ */
+ function getError(field: string): string | null {
+ return errors.value[field]?.message || null
+ }
+
+ /**
+ * Cek apakah ada error apapun
+ */
+ const hasErrors = computed(() => Object.keys(errors.value).length > 0)
+
+ /**
+ * Ambil semua error messages sebagai array
+ */
+ const errorMessages = computed(() =>
+ Object.values(errors.value).map(error => error.message),
+ )
+
+ /**
+ * Ambil error pertama (untuk menampilkan alert general)
+ */
+ const firstError = computed(() => {
+ const firstKey = Object.keys(errors.value)[0]
+ return firstKey ? errors.value[firstKey] : null
+ })
+
+ return {
+ errors: readonly(errors),
+ setFromZodError,
+ setErrors,
+ setError,
+ clearError,
+ clearErrors,
+ hasError,
+ getError,
+ hasErrors,
+ errorMessages,
+ firstError,
+ }
+}
diff --git a/app/composables/usePaginatedList.ts b/app/composables/usePaginatedList.ts
new file mode 100644
index 00000000..10f6863a
--- /dev/null
+++ b/app/composables/usePaginatedList.ts
@@ -0,0 +1,164 @@
+import type { DataTableLoader } from '~/components/pub/base/data-table/type'
+import type { PaginationMeta } from '~/components/pub/custom-ui/pagination/pagination.type'
+import { refDebounced, useUrlSearchParams } from '@vueuse/core'
+import * as z from 'zod'
+
+// Default query schema yang bisa digunakan semua list
+export const defaultQuerySchema = z.object({
+ q: z.union([z.literal(''), z.string().min(3)]).optional().catch(''),
+ page: z.coerce.number().int().min(1).default(1).catch(1),
+ pageSize: z.coerce.number().int().min(5).max(20).default(10).catch(10),
+})
+
+export const defaultQueryParams = {
+ q: '',
+ page: 1,
+ pageSize: 10,
+}
+
+export type DefaultQueryParams = z.infer
+
+interface UsePaginatedListOptions {
+ // Schema untuk validasi query parameters
+ querySchema?: z.ZodSchema
+ defaultQuery?: Record
+ // Fungsi untuk fetch data
+ fetchFn: (params: any) => Promise<{
+ success: boolean
+ body: {
+ data: T[]
+ meta: {
+ record_totalCount: number
+ }
+ }
+ }>
+ // Nama endpoint untuk logging error
+ entityName: string
+}
+
+export function usePaginatedList(options: UsePaginatedListOptions) {
+ const {
+ querySchema = defaultQuerySchema,
+ defaultQuery = defaultQueryParams,
+ fetchFn,
+ entityName,
+ } = options
+
+ // State management
+ const data = ref([])
+ const isLoading = reactive({
+ isTableLoading: false,
+ })
+
+ // URL state management
+ const queryParams = useUrlSearchParams('history', {
+ initialValue: defaultQuery,
+ removeFalsyValues: true,
+ })
+
+ const params = computed(() => {
+ const result = querySchema.safeParse(queryParams)
+ return result.data || defaultQuery
+ })
+
+ // Pagination state - computed from URL params
+ const paginationMeta = reactive({
+ recordCount: 0,
+ page: params.value.page,
+ pageSize: params.value.pageSize,
+ totalPage: 0,
+ hasNext: false,
+ hasPrev: false,
+ })
+
+ // Search model with debounce
+ const searchInput = ref(params.value.q || '')
+ const debouncedSearch = refDebounced(searchInput, 500) // 500ms debounce
+
+ // Functions
+ async function fetchData() {
+ isLoading.isTableLoading = true
+
+ try {
+ // Use current params from URL state
+ const currentParams = params.value
+
+ const response = await fetchFn(currentParams)
+
+ if (response.success) {
+ const responseBody = response.body
+ data.value = responseBody.data || []
+
+ const pager = responseBody.meta
+ // Update pagination meta from response
+ paginationMeta.recordCount = pager.record_totalCount
+ paginationMeta.page = currentParams.page
+ paginationMeta.pageSize = currentParams.pageSize
+ paginationMeta.totalPage = Math.ceil(pager.record_totalCount / paginationMeta.pageSize)
+ paginationMeta.hasNext = paginationMeta.page < paginationMeta.totalPage
+ paginationMeta.hasPrev = paginationMeta.page > 1
+ }
+ } catch (error) {
+ console.error(`Error fetching ${entityName} list:`, error)
+ data.value = []
+ paginationMeta.recordCount = 0
+ paginationMeta.totalPage = 0
+ paginationMeta.hasNext = false
+ paginationMeta.hasPrev = false
+ } finally {
+ isLoading.isTableLoading = false
+ }
+ }
+
+ // Handle pagination page change
+ function handlePageChange(page: number) {
+ // Update URL params - this will trigger watcher
+ queryParams.page = page
+ }
+
+ // Handle search from header component
+ function handleSearch(searchValue: string) {
+ // Update URL params - this will trigger watcher and refetch data
+ queryParams.q = searchValue
+ queryParams.page = 1 // Reset to first page when searching
+ }
+
+ // Watchers
+ // Watch for URL param changes and trigger refetch
+ watch(params, (newParams) => {
+ // Sync search input with URL params (for back/forward navigation)
+ if (newParams.q !== searchInput.value) {
+ searchInput.value = newParams.q || ''
+ }
+ fetchData()
+ }, { deep: true })
+
+ // Watch debounced search and update URL params (keeping for backward compatibility)
+ watch(debouncedSearch, (newValue) => {
+ // Only search if 3+ characters or empty (to clear search)
+ if (newValue.length === 0 || newValue.length >= 3) {
+ queryParams.q = newValue
+ queryParams.page = 1 // Reset to first page when searching
+ }
+ })
+
+ // Initialize data on mount
+ onMounted(() => {
+ fetchData()
+ })
+
+ return {
+ // State
+ data,
+ isLoading,
+ paginationMeta,
+ searchInput,
+ params,
+ queryParams,
+
+ // Functions
+ fetchData,
+ handlePageChange,
+ handleSearch,
+ }
+}
diff --git a/app/composables/useXfetch.ts b/app/composables/useXfetch.ts
index 3f27c07f..87d69f97 100644
--- a/app/composables/useXfetch.ts
+++ b/app/composables/useXfetch.ts
@@ -1,13 +1,5 @@
import type { Pinia } from 'pinia'
-
-export interface XError {
- code: string
- message: string
- expectedVal?: string
- givenVal?: string
-}
-
-export type XErrors = Record
+import type { XError, XErrors } from '~/types/error'
export interface XfetchResult {
success: boolean
diff --git a/app/models/item-price.ts b/app/models/item-price.ts
new file mode 100644
index 00000000..fb474109
--- /dev/null
+++ b/app/models/item-price.ts
@@ -0,0 +1,39 @@
+export interface ItemPrice {
+ id: string
+ item_id: number
+ price: number
+ insuranceCompany_code: string
+}
+
+export interface CreateDto {
+ item_id: number
+ price: number
+ insuranceCompany_code: string
+}
+
+export interface GetListDto {
+ page: number
+ size: number
+ name?: string
+ code?: string
+}
+
+export interface GetDetailDto {
+ id?: string
+}
+
+export interface UpdateDto extends CreateDto {
+ id?: number
+}
+
+export interface DeleteDto {
+ id?: string
+}
+
+export function genMedicine(): CreateDto {
+ return {
+ item_id: 1,
+ price: 1,
+ insuranceCompany_code: 'test',
+ }
+}
diff --git a/app/models/item.ts b/app/models/item.ts
new file mode 100644
index 00000000..851ff11d
--- /dev/null
+++ b/app/models/item.ts
@@ -0,0 +1,48 @@
+export interface Item {
+ id: string
+ name: string
+ code: string
+ itemGroup_code: string
+ uom_code: string
+ infra_id: number
+ stock: number
+}
+
+export interface CreateDto {
+ name: string
+ code: string
+ itemGroup_code: string
+ uom_code: string
+ infra_id: number
+ stock: number
+}
+
+export interface GetListDto {
+ page: number
+ size: number
+ name?: string
+ code?: string
+}
+
+export interface GetDetailDto {
+ id?: string
+}
+
+export interface UpdateDto extends CreateDto {
+ id?: number
+}
+
+export interface DeleteDto {
+ id?: string
+}
+
+export function genMedicine(): CreateDto {
+ return {
+ name: 'test',
+ code: 'test',
+ itemGroup_code: 'test',
+ uom_code: 'test',
+ infra_id: 1,
+ stock: 1,
+ }
+}
diff --git a/app/models/medicine-group.ts b/app/models/medicine-group.ts
new file mode 100644
index 00000000..afaa06c6
--- /dev/null
+++ b/app/models/medicine-group.ts
@@ -0,0 +1,36 @@
+export interface MedicineGroup {
+ id: string
+ name: string
+ code: string
+}
+
+export interface CreateDto {
+ name: string
+ code: string
+}
+
+export interface GetListDto {
+ page: number
+ size: number
+ name?: string
+ code?: string
+}
+
+export interface GetDetailDto {
+ id?: string
+}
+
+export interface UpdateDto extends CreateDto {
+ id?: number
+}
+
+export interface DeleteDto {
+ id?: string
+}
+
+export function genMedicine(): CreateDto {
+ return {
+ name: 'name',
+ code: 'code',
+ }
+}
diff --git a/app/models/medicine-method.ts b/app/models/medicine-method.ts
new file mode 100644
index 00000000..78e0a68b
--- /dev/null
+++ b/app/models/medicine-method.ts
@@ -0,0 +1,36 @@
+export interface MedicineMethod {
+ id: string
+ name: string
+ code: string
+}
+
+export interface CreateDto {
+ name: string
+ code: string
+}
+
+export interface GetListDto {
+ page: number
+ size: number
+ name?: string
+ code?: string
+}
+
+export interface GetDetailDto {
+ id?: string
+}
+
+export interface UpdateDto extends CreateDto {
+ id?: number
+}
+
+export interface DeleteDto {
+ id?: string
+}
+
+export function genMedicine(): CreateDto {
+ return {
+ name: 'name',
+ code: 'code',
+ }
+}
diff --git a/app/models/medicine.ts b/app/models/medicine.ts
new file mode 100644
index 00000000..382c76e2
--- /dev/null
+++ b/app/models/medicine.ts
@@ -0,0 +1,68 @@
+export interface Medicine {
+ id: string
+ name: string
+ code: string
+ medicineGroup_code: string
+ medicineMethod_code: string
+ uom_code: string
+ type: string
+ dose: string
+ infra_id: string
+ stock: string
+ status: string
+}
+
+export interface CreateDto {
+ name: string
+ code: string
+ medicineGroup_code: string
+ medicineMethod_code: string
+ uom_code: string
+ type: string
+ dose: string
+ infra_id: string
+ stock: string
+ status: string
+}
+
+export interface GetListDto {
+ page: number
+ size: number
+ name?: string
+ code?: string
+ medicineGroup_code?: string
+ medicineMethod_code?: string
+ uom_code?: string
+ type?: string
+ dose?: string
+ infra_id?: string
+ stock?: string
+ status?: string
+}
+
+export interface GetDetailDto {
+ id?: string
+}
+
+export interface UpdateDto extends CreateDto {
+ id?: number
+}
+
+export interface DeleteDto {
+ id?: string
+}
+
+export function genMedicine(): CreateDto {
+ return {
+ name: 'name',
+ code: 'code',
+ medicineGroup_code: 'medicineGroup_code',
+ medicineMethod_code: 'medicineMethod_code',
+ uom_code: 'uom_code',
+ type: 'type',
+ dose: 'dose',
+ infra_id: 'infra_id',
+ stock: 'stock',
+ status: 'status',
+ }
+}
diff --git a/app/pages/(features)/doctor/add.vue b/app/pages/(features)/doctor/add.vue
index 987baa28..c0a43c7f 100644
--- a/app/pages/(features)/doctor/add.vue
+++ b/app/pages/(features)/doctor/add.vue
@@ -35,7 +35,7 @@ const canCreate = hasCreateAccess(roleAccess)
-
+
diff --git a/app/pages/(features)/doctor/index.vue b/app/pages/(features)/doctor/index.vue
index e75a5b89..f0c8ddba 100644
--- a/app/pages/(features)/doctor/index.vue
+++ b/app/pages/(features)/doctor/index.vue
@@ -33,7 +33,7 @@ const canRead = hasReadAccess(roleAccess)
diff --git a/app/pages/(features)/human-src/user/add.vue b/app/pages/(features)/human-src/user/add.vue
index 39ba8791..8478c9b2 100644
--- a/app/pages/(features)/human-src/user/add.vue
+++ b/app/pages/(features)/human-src/user/add.vue
@@ -35,7 +35,7 @@ const canCreate = hasCreateAccess(roleAccess)
-
+
diff --git a/app/pages/(features)/human-src/user/index.vue b/app/pages/(features)/human-src/user/index.vue
index 2c0f921f..e8e3647e 100644
--- a/app/pages/(features)/human-src/user/index.vue
+++ b/app/pages/(features)/human-src/user/index.vue
@@ -33,7 +33,7 @@ const canRead = hasReadAccess(roleAccess)
diff --git a/app/pages/(features)/org-src/division/index.vue b/app/pages/(features)/org-src/division/index.vue
new file mode 100644
index 00000000..aa674b28
--- /dev/null
+++ b/app/pages/(features)/org-src/division/index.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
diff --git a/app/pages/(features)/org-src/installation/index.vue b/app/pages/(features)/org-src/installation/index.vue
new file mode 100644
index 00000000..4131d387
--- /dev/null
+++ b/app/pages/(features)/org-src/installation/index.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
diff --git a/app/pages/(features)/org-src/unit/index.vue b/app/pages/(features)/org-src/unit/index.vue
new file mode 100644
index 00000000..801105eb
--- /dev/null
+++ b/app/pages/(features)/org-src/unit/index.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
diff --git a/app/pages/(features)/patient/add.vue b/app/pages/(features)/patient/add.vue
index 813e418b..6532240a 100644
--- a/app/pages/(features)/patient/add.vue
+++ b/app/pages/(features)/patient/add.vue
@@ -35,7 +35,7 @@ const canCreate = hasCreateAccess(roleAccess)
-
+
diff --git a/app/pages/(features)/patient/index.vue b/app/pages/(features)/patient/index.vue
index 71d07e43..8f274db1 100644
--- a/app/pages/(features)/patient/index.vue
+++ b/app/pages/(features)/patient/index.vue
@@ -33,7 +33,7 @@ const canRead = hasReadAccess(roleAccess)
diff --git a/app/pages/(features)/rehab/registration-queue/sep-prosedur/add.vue b/app/pages/(features)/rehab/registration-queue/sep-prosedur/add.vue
index 368b4f40..e4381443 100644
--- a/app/pages/(features)/rehab/registration-queue/sep-prosedur/add.vue
+++ b/app/pages/(features)/rehab/registration-queue/sep-prosedur/add.vue
@@ -34,7 +34,7 @@ const canCreate = hasCreateAccess(roleAccess)
-
+
diff --git a/app/pages/(features)/satusehat/add.vue b/app/pages/(features)/satusehat/add.vue
index d1fee557..1de89d94 100644
--- a/app/pages/(features)/satusehat/add.vue
+++ b/app/pages/(features)/satusehat/add.vue
@@ -34,7 +34,7 @@ const canCreate = hasCreateAccess(roleAccess)
-
+
diff --git a/app/pages/(features)/satusehat/index.vue b/app/pages/(features)/satusehat/index.vue
index 2c7bb9f5..69126581 100644
--- a/app/pages/(features)/satusehat/index.vue
+++ b/app/pages/(features)/satusehat/index.vue
@@ -32,7 +32,7 @@ const canRead = hasReadAccess(roleAccess)
diff --git a/app/pages/(features)/tools-equipment-src/device/add.vue b/app/pages/(features)/tools-equipment-src/device/add.vue
new file mode 100644
index 00000000..54f4baff
--- /dev/null
+++ b/app/pages/(features)/tools-equipment-src/device/add.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
diff --git a/app/pages/(features)/tools-equipment-src/device/index.vue b/app/pages/(features)/tools-equipment-src/device/index.vue
new file mode 100644
index 00000000..464e5558
--- /dev/null
+++ b/app/pages/(features)/tools-equipment-src/device/index.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
diff --git a/app/pages/(features)/tools-equipment-src/medicine-method/index.vue b/app/pages/(features)/tools-equipment-src/medicine-method/index.vue
new file mode 100644
index 00000000..29383fb4
--- /dev/null
+++ b/app/pages/(features)/tools-equipment-src/medicine-method/index.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
diff --git a/app/pages/(features)/tools-equipment-src/medicine-type/index.vue b/app/pages/(features)/tools-equipment-src/medicine-type/index.vue
new file mode 100644
index 00000000..cc8d6afe
--- /dev/null
+++ b/app/pages/(features)/tools-equipment-src/medicine-type/index.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
diff --git a/app/pages/(features)/tools-equipment-src/medicine/[id]/edit.vue b/app/pages/(features)/tools-equipment-src/medicine/[id]/edit.vue
new file mode 100644
index 00000000..adf77a76
--- /dev/null
+++ b/app/pages/(features)/tools-equipment-src/medicine/[id]/edit.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
diff --git a/app/pages/(features)/tools-equipment-src/medicine/add.vue b/app/pages/(features)/tools-equipment-src/medicine/add.vue
new file mode 100644
index 00000000..adf77a76
--- /dev/null
+++ b/app/pages/(features)/tools-equipment-src/medicine/add.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
diff --git a/app/pages/(features)/tools-equipment-src/medicine/index.vue b/app/pages/(features)/tools-equipment-src/medicine/index.vue
new file mode 100644
index 00000000..3c8b58da
--- /dev/null
+++ b/app/pages/(features)/tools-equipment-src/medicine/index.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
diff --git a/app/pages/_dev/index.vue b/app/pages/_dev/index.vue
index b1afab26..b36abf7e 100644
--- a/app/pages/_dev/index.vue
+++ b/app/pages/_dev/index.vue
@@ -46,6 +46,11 @@ const navMenu = ref({
icon: 'i-lucide-user',
component: defineAsyncComponent(() => import('~/pages/_dev/user/list.vue')),
},
+ {
+ title: 'Test Medicine List',
+ icon: 'i-lucide-user',
+ component: defineAsyncComponent(() => import('~/components/content/item/list.vue')),
+ },
],
})
diff --git a/app/pages/auth/login.vue b/app/pages/auth/login.vue
index b2e8f89a..9727a6be 100644
--- a/app/pages/auth/login.vue
+++ b/app/pages/auth/login.vue
@@ -11,7 +11,7 @@ definePageMeta({
Login RSSA
-
+
diff --git a/app/pages/index.vue b/app/pages/index.vue
index 5d6072e5..45476961 100644
--- a/app/pages/index.vue
+++ b/app/pages/index.vue
@@ -13,5 +13,5 @@ useHead({
diff --git a/app/schemas/auth.schema.ts b/app/schemas/auth.schema.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/app/services/auth.service.ts b/app/services/auth.service.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/app/services/medicine-method.service.ts b/app/services/medicine-method.service.ts
new file mode 100644
index 00000000..02327163
--- /dev/null
+++ b/app/services/medicine-method.service.ts
@@ -0,0 +1,9 @@
+import { xfetch } from '~/composables/useXfetch'
+
+export async function getMedicineMethods() {
+ const resp = await xfetch('/api/v1/medicine-method')
+ if (resp.success) {
+ return (resp.body as Record