adjustment on picker dialog procedure
refactor(procedure-picker): improve component structure and add readonly support - Reorganize imports and add type imports section - Replace custom Button with ButtonAction component - Add readonly state handling for fields and buttons - Improve type safety with ProcedureSrc type casting feat(usePaginatedList): add syncToUrl option for nested components Add syncToUrl option to disable URL synchronization when used in modals or nested components to prevent overriding parent page URL. Default remains true for backward compatibility. Also includes minor formatting improvements in procedure-list.vue template.
This commit is contained in:
No files matched your search
+19
-14
@@ -45,6 +45,7 @@ const {
|
|||||||
handleSearch,
|
handleSearch,
|
||||||
fetchData: getItemList,
|
fetchData: getItemList,
|
||||||
} = usePaginatedList({
|
} = usePaginatedList({
|
||||||
|
syncToUrl: false,
|
||||||
fetchFn: async (params: any) => {
|
fetchFn: async (params: any) => {
|
||||||
const result = await getList({
|
const result = await getList({
|
||||||
search: params.search,
|
search: params.search,
|
||||||
@@ -102,19 +103,23 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Dialog v-model:open="isModalOpen" title="" size="xl">
|
<Dialog
|
||||||
<Header
|
v-model:open="isModalOpen"
|
||||||
v-model="searchInput"
|
title=""
|
||||||
:prep="headerPrep"
|
size="xl"
|
||||||
:ref-search-nav="headerPrep.refSearchNav"
|
>
|
||||||
@search="handleSearch"
|
<Header
|
||||||
class=""
|
v-model="searchInput"
|
||||||
/>
|
:prep="headerPrep"
|
||||||
<AppProcedureSrcList
|
:ref-search-nav="headerPrep.refSearchNav"
|
||||||
:table-config="config"
|
@search="handleSearch"
|
||||||
:data="data"
|
class=""
|
||||||
:pagination-meta="paginationMeta"
|
/>
|
||||||
@page-change="handlePageChange"
|
<AppProcedureSrcList
|
||||||
/>
|
:table-config="config"
|
||||||
|
:data="data"
|
||||||
|
:pagination-meta="paginationMeta"
|
||||||
|
@page-change="handlePageChange"
|
||||||
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
+30
-33
@@ -1,23 +1,24 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ProcedureListDialog from './procedure-list.vue'
|
|
||||||
import type { FormErrors } from '~/types/error'
|
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
|
||||||
import { Form } from '~/components/pub/ui/form'
|
|
||||||
import { FieldArray } from 'vee-validate'
|
|
||||||
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
|
|
||||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
|
||||||
import TextAreaInput from '~/components/pub/my-ui/form/text-area-input.vue'
|
|
||||||
import { cn } from '~/lib/utils'
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
|
// types
|
||||||
|
import { type ProcedureSrc } from '~/models/procedure-src'
|
||||||
|
|
||||||
|
// componenets
|
||||||
|
import { FieldArray } from 'vee-validate'
|
||||||
|
import { ButtonAction } from '~/components/pub/my-ui/form'
|
||||||
import TableHeader from '~/components/pub/ui/table/TableHeader.vue'
|
import TableHeader from '~/components/pub/ui/table/TableHeader.vue'
|
||||||
import { is } from 'date-fns/locale'
|
import ProcedureListDialog from './procedure-list.vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
fieldName: string
|
fieldName: string
|
||||||
title: string
|
title: string
|
||||||
subTitle?: string
|
subTitle?: string
|
||||||
|
isReadonly?: boolean
|
||||||
}
|
}
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { isReadonly = false } = props
|
||||||
const isProcedurePickerDialogOpen = ref<boolean>(false)
|
const isProcedurePickerDialogOpen = ref<boolean>(false)
|
||||||
provide(`isProcedurePickerDialogOpen`, isProcedurePickerDialogOpen)
|
provide(`isProcedurePickerDialogOpen`, isProcedurePickerDialogOpen)
|
||||||
</script>
|
</script>
|
||||||
@@ -26,19 +27,15 @@ provide(`isProcedurePickerDialogOpen`, isProcedurePickerDialogOpen)
|
|||||||
<div class="">
|
<div class="">
|
||||||
<div class="mb-2 flex items-center justify-between">
|
<div class="mb-2 flex items-center justify-between">
|
||||||
<p class="mb-2 font-medium">{{ title }}</p>
|
<p class="mb-2 font-medium">{{ title }}</p>
|
||||||
<Button
|
<ButtonAction
|
||||||
type="button"
|
preset="add"
|
||||||
|
title="Tambah Item"
|
||||||
|
icon="i-lucide-search"
|
||||||
|
:disabled="isReadonly"
|
||||||
|
:label="subTitle || 'Pilih Diagnosis'"
|
||||||
|
:full-width-mobile="true"
|
||||||
@click="isProcedurePickerDialogOpen = true"
|
@click="isProcedurePickerDialogOpen = true"
|
||||||
size="xs"
|
/>
|
||||||
variant="outline"
|
|
||||||
class="border-orange-400 bg-transparent text-orange-400"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name="i-lucide-search"
|
|
||||||
class="h-4 w-4 align-middle transition-colors"
|
|
||||||
/>
|
|
||||||
{{ subTitle || 'Pilih Diagnosis' }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FieldArray
|
<FieldArray
|
||||||
@@ -61,20 +58,20 @@ provide(`isProcedurePickerDialogOpen`, isProcedurePickerDialogOpen)
|
|||||||
v-for="(field, idx) in fields"
|
v-for="(field, idx) in fields"
|
||||||
:key="idx"
|
:key="idx"
|
||||||
>
|
>
|
||||||
<TableCell class="">{{ field.value?.name }}</TableCell>
|
<TableCell :class="cn(isReadonly && 'text-muted-foreground')">
|
||||||
<TableCell class="">{{ field.value?.code }}</TableCell>
|
{{ (field.value as ProcedureSrc)?.name }}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell :class="cn(isReadonly && 'text-muted-foreground')">
|
||||||
|
{{ (field.value as ProcedureSrc)?.code }}
|
||||||
|
</TableCell>
|
||||||
<TableCell class="">
|
<TableCell class="">
|
||||||
<Button
|
<ButtonAction
|
||||||
type="button"
|
:disabled="isReadonly"
|
||||||
variant="destructive"
|
preset="delete"
|
||||||
size="sm"
|
:title="`Hapus prosedur '${(field.value as ProcedureSrc)?.name}'`"
|
||||||
|
icon-only
|
||||||
@click="remove(idx)"
|
@click="remove(idx)"
|
||||||
>
|
/>
|
||||||
<Icon
|
|
||||||
name="i-lucide-trash-2"
|
|
||||||
class="h-4 w-4"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import type { DataTableLoader } from '~/components/pub/my-ui/data-table/type'
|
|||||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||||
import { refDebounced, useUrlSearchParams } from '@vueuse/core'
|
import { refDebounced, useUrlSearchParams } from '@vueuse/core'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
import { is } from "date-fns/locale"
|
|
||||||
|
|
||||||
// Default query schema yang bisa digunakan semua list
|
// Default query schema yang bisa digunakan semua list
|
||||||
export const defaultQuerySchema = z.object({
|
export const defaultQuerySchema = z.object({
|
||||||
@@ -38,10 +37,23 @@ interface UsePaginatedListOptions<T = any> {
|
|||||||
}>
|
}>
|
||||||
// Nama endpoint untuk logging error
|
// Nama endpoint untuk logging error
|
||||||
entityName: string
|
entityName: string
|
||||||
|
/**
|
||||||
|
* Apakah state harus disinkronkan ke URL Browser?
|
||||||
|
* Set `false` jika digunakan di dalam Modal, Drawer, atau Nested Component
|
||||||
|
* agar tidak menimpa URL halaman induk.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
syncToUrl?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePaginatedList<T = any>(options: UsePaginatedListOptions<T>) {
|
export function usePaginatedList<T = any>(options: UsePaginatedListOptions<T>) {
|
||||||
const { querySchema = defaultQuerySchema, defaultQuery = defaultQueryParams, fetchFn, entityName } = options
|
const {
|
||||||
|
querySchema = defaultQuerySchema,
|
||||||
|
defaultQuery = defaultQueryParams,
|
||||||
|
fetchFn,
|
||||||
|
entityName,
|
||||||
|
syncToUrl = true, // Default true agar behavior lama tetap jalan
|
||||||
|
} = options
|
||||||
|
|
||||||
// State management
|
// State management
|
||||||
const data = ref<T[]>([])
|
const data = ref<T[]>([])
|
||||||
@@ -49,11 +61,19 @@ export function usePaginatedList<T = any>(options: UsePaginatedListOptions<T>) {
|
|||||||
isTableLoading: false,
|
isTableLoading: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
// URL state management
|
let queryParams: any
|
||||||
const queryParams = useUrlSearchParams('history', {
|
|
||||||
initialValue: defaultQuery,
|
if (syncToUrl) {
|
||||||
removeFalsyValues: true,
|
// Mode Halaman Utama: Sync ke URL
|
||||||
})
|
queryParams = useUrlSearchParams('history', {
|
||||||
|
initialValue: defaultQuery,
|
||||||
|
removeFalsyValues: true,
|
||||||
|
write: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Mode Nested/Modal: Local Reactive State
|
||||||
|
queryParams = reactive({ ...defaultQuery })
|
||||||
|
}
|
||||||
|
|
||||||
const params = computed(() => {
|
const params = computed(() => {
|
||||||
const result = querySchema.safeParse(queryParams)
|
const result = querySchema.safeParse(queryParams)
|
||||||
@@ -168,7 +188,7 @@ export function usePaginatedList<T = any>(options: UsePaginatedListOptions<T>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transform(endpoint: string ,params: any): string {
|
export function transform(endpoint: string, params: any): string {
|
||||||
const urlParams = new URLSearchParams()
|
const urlParams = new URLSearchParams()
|
||||||
|
|
||||||
Object.entries(params).forEach(([key, value]) => {
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user