refactor(material): modify validation uom and item

This commit is contained in:
riefive
2025-09-04 16:21:10 +07:00
parent 21d087e496
commit ffa4a7d191
5 changed files with 41 additions and 27 deletions

No files matched your search

+16 -14
View File
@@ -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
+17 -6
View File
@@ -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>
+2 -1
View File
@@ -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 -3
View File
@@ -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>