refactor(material): modify validation uom and item
This commit is contained in:
No files matched your search
@@ -11,6 +11,7 @@ import Label from '~/components/pub/custom-ui/form/label.vue'
|
|||||||
interface Props {
|
interface Props {
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
schema: z.ZodSchema<any>
|
schema: z.ZodSchema<any>
|
||||||
|
uoms: any[]
|
||||||
items: any[]
|
items: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,11 +21,13 @@ const emit = defineEmits<{
|
|||||||
submit: [data: any]
|
submit: [data: any]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { handleSubmit, defineField, errors, meta } = useForm({
|
const { handleSubmit, defineField, errors } = useForm({
|
||||||
validationSchema: toTypedSchema(props.schema),
|
validationSchema: toTypedSchema(props.schema),
|
||||||
initialValues: {
|
initialValues: {
|
||||||
code: '',
|
code: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
uom_code: '',
|
||||||
|
item_id: '',
|
||||||
stock: 0,
|
stock: 0,
|
||||||
} as Partial<MaterialFormData>,
|
} as Partial<MaterialFormData>,
|
||||||
})
|
})
|
||||||
@@ -36,7 +39,6 @@ const [item, itemAttrs] = defineField('item_id')
|
|||||||
const [stock, stockAttrs] = defineField('stock')
|
const [stock, stockAttrs] = defineField('stock')
|
||||||
|
|
||||||
const onSubmit = handleSubmit(async (values) => {
|
const onSubmit = handleSubmit(async (values) => {
|
||||||
console.log(errors)
|
|
||||||
try {
|
try {
|
||||||
emit('submit', values)
|
emit('submit', values)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -77,30 +79,32 @@ const onSubmit = handleSubmit(async (values) => {
|
|||||||
<Label for="uom">Satuan</Label>
|
<Label for="uom">Satuan</Label>
|
||||||
<Select
|
<Select
|
||||||
id="uom"
|
id="uom"
|
||||||
:items="items"
|
icon-name="i-lucide-chevron-down"
|
||||||
placeholder="Pilih satuan"
|
placeholder="Pilih satuan"
|
||||||
v-model="uom"
|
v-model="uom"
|
||||||
v-bind="uomAttrs"
|
v-bind="uomAttrs"
|
||||||
|
:items="uoms"
|
||||||
:disabled="isLoading"
|
:disabled="isLoading"
|
||||||
:class="{ 'border-red-500': errors.uom }"
|
:class="{ 'border-red-500': errors.uom_code }"
|
||||||
/>
|
/>
|
||||||
<span v-if="errors.uom" class="text-sm text-red-500">
|
<span v-if="errors.uom_code" class="text-sm text-red-500">
|
||||||
{{ errors.uom }}
|
{{ errors.uom_code }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
<Label for="item">Item</Label>
|
<Label for="item">Item</Label>
|
||||||
<Select
|
<Select
|
||||||
id="item"
|
id="item"
|
||||||
:items="items"
|
icon-name="i-lucide-chevron-down"
|
||||||
placeholder="Pilih item"
|
placeholder="Pilih item"
|
||||||
v-model="item"
|
v-model="item"
|
||||||
v-bind="itemAttrs"
|
v-bind="itemAttrs"
|
||||||
|
:items="items"
|
||||||
:disabled="isLoading"
|
:disabled="isLoading"
|
||||||
:class="{ 'border-red-500': errors.item }"
|
:class="{ 'border-red-500': errors.item_id }"
|
||||||
/>
|
/>
|
||||||
<span v-if="errors.item" class="text-sm text-red-500">
|
<span v-if="errors.item_id" class="text-sm text-red-500">
|
||||||
{{ errors.uom }}
|
{{ errors.item_id }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
@@ -117,10 +121,8 @@ const onSubmit = handleSubmit(async (values) => {
|
|||||||
{{ errors.stock }}
|
{{ errors.stock }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end gap-2 my-2 py-2">
|
<div class="my-2 flex justify-end gap-2 py-2">
|
||||||
<Button variant="secondary" class="w-[120px]" @click="emit('back')">
|
<Button variant="secondary" class="w-[120px]" @click="emit('back')"> Kembali </Button>
|
||||||
Kembali
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" class="w-[120px]">
|
<Button type="submit" class="w-[120px]">
|
||||||
<Loader2 v-if="isLoading" class="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 v-if="isLoading" class="mr-2 h-4 w-4 animate-spin" />
|
||||||
Simpan
|
Simpan
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// types
|
// types
|
||||||
import { MaterialSchema, type MaterialFormData } from '~/schemas/material'
|
import { MaterialSchema, type MaterialFormData } from '~/schemas/material'
|
||||||
// components
|
|
||||||
import Action from '~/components/pub/custom-ui/nav-footer/ba-dr-su.vue'
|
|
||||||
|
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
const uoms = [
|
||||||
|
{ value: 'uom-1', label: 'Satuan 1' },
|
||||||
|
{ value: 'uom-2', label: 'Satuan 2' },
|
||||||
|
{ value: 'uom-3', label: 'Satuan 3' },
|
||||||
|
]
|
||||||
const items = [
|
const items = [
|
||||||
{ value: 'item1', label: 'Item 1' },
|
{ value: 'item-1', label: 'Item 1' },
|
||||||
{ value: 'item2', label: 'Item 2' },
|
{ value: 'item-2', label: 'Item 2' },
|
||||||
|
{ value: 'item-3', label: 'Item 3' },
|
||||||
]
|
]
|
||||||
|
|
||||||
function onBack() {
|
function onBack() {
|
||||||
@@ -15,7 +19,7 @@ function onBack() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onSubmit(data: MaterialFormData) {
|
async function onSubmit(data: MaterialFormData) {
|
||||||
console.log(data);
|
console.log(data)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -24,5 +28,12 @@ async function onSubmit(data: MaterialFormData) {
|
|||||||
<Icon name="i-lucide-panel-bottom" class="me-2" />
|
<Icon name="i-lucide-panel-bottom" class="me-2" />
|
||||||
<span class="font-semibold">Tambah</span> Perlengkapan (BMHP)
|
<span class="font-semibold">Tambah</span> Perlengkapan (BMHP)
|
||||||
</div>
|
</div>
|
||||||
<AppMaterialEntryForm :is-loading="isLoading" :schema="MaterialSchema" :items="items" @back="onBack" @submit="onSubmit" />
|
<AppMaterialEntryForm
|
||||||
|
:is-loading="isLoading"
|
||||||
|
:schema="MaterialSchema"
|
||||||
|
:uoms="uoms"
|
||||||
|
:items="items"
|
||||||
|
@back="onBack"
|
||||||
|
@submit="onSubmit"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -19,6 +19,7 @@ interface Item {
|
|||||||
const props = defineProps<
|
const props = defineProps<
|
||||||
SelectRootProps & {
|
SelectRootProps & {
|
||||||
items: Item[]
|
items: Item[]
|
||||||
|
iconName?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
label?: string
|
label?: string
|
||||||
separator?: boolean
|
separator?: boolean
|
||||||
@@ -31,7 +32,7 @@ const forwarded = useForwardPropsEmits(props, emits)
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SelectRoot v-bind="forwarded">
|
<SelectRoot v-bind="forwarded">
|
||||||
<SelectTrigger class="">
|
<SelectTrigger :icon-name="iconName" class="flex justify-between items-center">
|
||||||
<SelectValue :placeholder="placeholder" />
|
<SelectValue :placeholder="placeholder" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ import { SelectIcon, SelectTrigger, useForwardProps } from 'radix-vue'
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { cn } from '~/lib/utils'
|
import { cn } from '~/lib/utils'
|
||||||
|
|
||||||
const props = defineProps<SelectTriggerProps & { class?: HTMLAttributes['class'] }>()
|
const props = defineProps<SelectTriggerProps & { class?: HTMLAttributes['class'], iconName?: string }>()
|
||||||
|
|
||||||
const delegatedProps = computed(() => {
|
const delegatedProps = computed(() => {
|
||||||
const { class: _, ...delegated } = props
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
return delegated
|
return delegated
|
||||||
})
|
})
|
||||||
|
|
||||||
const forwardedProps = useForwardProps(delegatedProps)
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
const iconName = computed(() => props.iconName || 'i-radix-icons-caret-sort')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -28,7 +28,7 @@ const forwardedProps = useForwardProps(delegatedProps)
|
|||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
<SelectIcon as-child>
|
<SelectIcon as-child>
|
||||||
<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>
|
||||||
@@ -3,9 +3,9 @@ import { z } from 'zod'
|
|||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
code: z.string({ required_error: 'Kode harus diisi' }).min(1, 'Kode minimum 1 karakter'),
|
code: z.string({ required_error: 'Kode harus diisi' }).min(1, 'Kode minimum 1 karakter'),
|
||||||
name: z.string({ required_error: 'Nama harus diisi' }).min(1, 'Nama minimum 1 karakter'),
|
name: z.string({ required_error: 'Nama harus diisi' }).min(1, 'Nama minimum 1 karakter'),
|
||||||
uom_code: z.string({ required_error: 'Kode unit harus diisi' }),
|
uom_code: z.string({ required_error: 'Kode unit harus diisi' }).min(1, 'Kode unit harus diisi'),
|
||||||
item_id: z.string({ required_error: 'Tipe harus diisi' }),
|
item_id: z.string({ required_error: 'Tipe harus diisi' }).min(1, 'Tipe harus diisi'),
|
||||||
stock: z.preprocess((val) => Number(val), z.number({ invalid_type_error: 'Stok harus berupa angka' }).min(1)),
|
stock: z.preprocess((val) => Number(val), z.number({ invalid_type_error: 'Stok harus berupa angka' }).min(1, 'Stok harus lebih besar dari 0')),
|
||||||
})
|
})
|
||||||
|
|
||||||
type formData = z.infer<typeof schema>
|
type formData = z.infer<typeof schema>
|
||||||
|
|||||||
Reference in New Issue
Block a user