Refactor form error handling in various components to utilize a centralized formErrors function. This change improves code consistency and readability by standardizing error message retrieval across LoginForm, CashTransactionFormModal, EmployeeAdvanceFormModal, ExpenseFormModal, and others. Update error handling in fields for better user feedback.
This commit is contained in:
parent
1e32837d76
commit
09f35db9d8
@ -10,6 +10,7 @@ import {
|
||||
FieldLabel
|
||||
} from '@/components/ui/field'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { formErrors } from '@/lib/form'
|
||||
import { cn } from "@/lib/utils"
|
||||
import FieldSeparator from './ui/field/FieldSeparator.vue'
|
||||
|
||||
@ -54,7 +55,7 @@ function submit() {
|
||||
Email/Username
|
||||
</FieldLabel>
|
||||
<Input id="login" v-model="form.login" type="text" placeholder="m@example.com" required />
|
||||
<FieldError :errors="form.errors.login ? [form.errors.login] : []" />
|
||||
<FieldError :errors="formErrors(form, 'login')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<div class="flex items-center">
|
||||
@ -67,7 +68,7 @@ function submit() {
|
||||
</div>
|
||||
<Input id="password" v-model="form.password" type="password" placeholder="********"
|
||||
required />
|
||||
<FieldError :errors="form.errors.password ? [form.errors.password] : []" />
|
||||
<FieldError :errors="formErrors(form, 'password')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<Button type="submit" :disabled="form.processing">
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
} from '@/components/ui/field';
|
||||
import { RupiahInput } from '@/components/ui/rupiah-input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import { parseRupiah } from '@/lib/rupiah';
|
||||
import type { CashTransactionListItem } from '@/types/cash';
|
||||
import { appendRootPhotosToFormData, createMediaUploadState } from '@/types/media';
|
||||
@ -86,10 +87,6 @@ function buildFormData(forUpdate: boolean): FormData {
|
||||
return formData;
|
||||
}
|
||||
|
||||
function formError(key: string): string | undefined {
|
||||
return (form.errors as Record<string, string>)[key];
|
||||
}
|
||||
|
||||
function submit() {
|
||||
const options = {
|
||||
forceFormData: true,
|
||||
@ -127,16 +124,16 @@ function submit() {
|
||||
<Field>
|
||||
<FieldLabel for="cash-amount" required>Jumlah</FieldLabel>
|
||||
<RupiahInput id="cash-amount" v-model="form.amount" autofocus />
|
||||
<FieldError :errors="form.errors.amount ? [form.errors.amount] : []" />
|
||||
<FieldError :errors="formErrors(form, 'amount')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="cash-description" required>Keterangan</FieldLabel>
|
||||
<Textarea id="cash-description" v-model="form.description"
|
||||
placeholder="Contoh: Setoran kas harian" rows="3" />
|
||||
<FieldError :errors="form.errors.description ? [form.errors.description] : []" />
|
||||
<FieldError :errors="formErrors(form, 'description')" />
|
||||
</Field>
|
||||
<MediaImageUpload id="cash-photos" v-model="form.media" label="Foto Bukti" required
|
||||
:errors="formError('photos') ? [formError('photos')!] : []" />
|
||||
:errors="formErrors(form, 'photos')" />
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
} from '@/components/ui/field';
|
||||
import { RupiahInput } from '@/components/ui/rupiah-input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import { parseRupiah } from '@/lib/rupiah';
|
||||
import type { EmployeeAdvanceListItem } from '@/types/employee-advance';
|
||||
import { appendRootPhotosToFormData, createMediaUploadState } from '@/types/media';
|
||||
@ -90,10 +91,6 @@ function buildFormData(forUpdate: boolean): FormData {
|
||||
return formData;
|
||||
}
|
||||
|
||||
function formError(key: string): string | undefined {
|
||||
return (form.errors as Record<string, string>)[key];
|
||||
}
|
||||
|
||||
function submit() {
|
||||
const options = {
|
||||
forceFormData: true,
|
||||
@ -129,21 +126,21 @@ function submit() {
|
||||
<Field>
|
||||
<FieldLabel for="employee-advance-amount" required>Jumlah</FieldLabel>
|
||||
<RupiahInput id="employee-advance-amount" v-model="form.amount" autofocus />
|
||||
<FieldError :errors="form.errors.amount ? [form.errors.amount] : []" />
|
||||
<FieldError :errors="formErrors(form, 'amount')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="employee-advance-description" required>Keterangan</FieldLabel>
|
||||
<Textarea id="employee-advance-description" v-model="form.description"
|
||||
placeholder="Contoh: Kebutuhan mendesak" rows="3" />
|
||||
<FieldError :errors="form.errors.description ? [form.errors.description] : []" />
|
||||
<FieldError :errors="formErrors(form, 'description')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="employee-advance-due-date" required>Jatuh Tempo</FieldLabel>
|
||||
<DatePicker id="employee-advance-due-date" v-model="form.due_date" />
|
||||
<FieldError :errors="form.errors.due_date ? [form.errors.due_date] : []" />
|
||||
<FieldError :errors="formErrors(form, 'due_date')" />
|
||||
</Field>
|
||||
<MediaImageUpload id="employee-advance-photos" v-model="form.media" label="Foto Bukti" required
|
||||
:errors="formError('photos') ? [formError('photos')!] : []" />
|
||||
:errors="formErrors(form, 'photos')" />
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
FieldSet,
|
||||
} from '@/components/ui/field';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import type { EmployeeAdvanceListItem } from '@/types/employee-advance';
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false });
|
||||
@ -73,7 +74,7 @@ function submit() {
|
||||
<FieldLabel for="reject-reason" required>Alasan Penolakan</FieldLabel>
|
||||
<Textarea id="reject-reason" v-model="form.reason" placeholder="Tuliskan alasan penolakan"
|
||||
rows="3" autofocus />
|
||||
<FieldError :errors="form.errors.reason ? [form.errors.reason] : []" />
|
||||
<FieldError :errors="formErrors(form, 'reason')" />
|
||||
</Field>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
} from '@/components/ui/field';
|
||||
import { RupiahInput } from '@/components/ui/rupiah-input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import { parseRupiah } from '@/lib/rupiah';
|
||||
import type { ExpenseListItem } from '@/types/expense';
|
||||
import { appendRootPhotosToFormData, createMediaUploadState } from '@/types/media';
|
||||
@ -86,10 +87,6 @@ function buildFormData(forUpdate: boolean): FormData {
|
||||
return formData;
|
||||
}
|
||||
|
||||
function formError(key: string): string | undefined {
|
||||
return (form.errors as Record<string, string>)[key];
|
||||
}
|
||||
|
||||
function submit() {
|
||||
const options = {
|
||||
forceFormData: true,
|
||||
@ -125,16 +122,16 @@ function submit() {
|
||||
<Field>
|
||||
<FieldLabel for="expense-amount" required>Jumlah</FieldLabel>
|
||||
<RupiahInput id="expense-amount" v-model="form.amount" autofocus />
|
||||
<FieldError :errors="form.errors.amount ? [form.errors.amount] : []" />
|
||||
<FieldError :errors="formErrors(form, 'amount')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="expense-description" required>Keterangan</FieldLabel>
|
||||
<Textarea id="expense-description" v-model="form.description"
|
||||
placeholder="Contoh: Pembelian perlengkapan toko" rows="3" />
|
||||
<FieldError :errors="form.errors.description ? [form.errors.description] : []" />
|
||||
<FieldError :errors="formErrors(form, 'description')" />
|
||||
</Field>
|
||||
<MediaImageUpload id="expense-photos" v-model="form.media" label="Foto Bukti" required
|
||||
:errors="formError('photos') ? [formError('photos')!] : []" />
|
||||
:errors="formErrors(form, 'photos')" />
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import type { EmployeeFormData, EnumOption } from '@/types/employee';
|
||||
|
||||
const props = withDefaults(
|
||||
@ -89,12 +90,12 @@ function submit() {
|
||||
<Field>
|
||||
<FieldLabel for="email" required>Email</FieldLabel>
|
||||
<Input id="email" v-model="form.email" type="email" placeholder="nama@perusahaan.com" />
|
||||
<FieldError :errors="form.errors.email ? [form.errors.email] : []" />
|
||||
<FieldError :errors="formErrors(form, 'email')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="username" required>Username</FieldLabel>
|
||||
<Input id="username" v-model="form.username" type="text" placeholder="username" />
|
||||
<FieldError :errors="form.errors.username ? [form.errors.username] : []" />
|
||||
<FieldError :errors="formErrors(form, 'username')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="role" required>Role</FieldLabel>
|
||||
@ -108,7 +109,7 @@ function submit() {
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FieldError :errors="form.errors.role ? [form.errors.role] : []" />
|
||||
<FieldError :errors="formErrors(form, 'role')" />
|
||||
</Field>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
@ -126,18 +127,18 @@ function submit() {
|
||||
<FieldLabel for="full_name" required>Nama Lengkap</FieldLabel>
|
||||
<Input id="full_name" v-model="form.full_name" type="text"
|
||||
placeholder="Nama lengkap pegawai" />
|
||||
<FieldError :errors="form.errors.full_name ? [form.errors.full_name] : []" />
|
||||
<FieldError :errors="formErrors(form, 'full_name')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="phone_number">Nomor Telepon</FieldLabel>
|
||||
<PhoneNumberInput id="phone_number" v-model="form.phone_number" />
|
||||
<FieldError :errors="form.errors.phone_number ? [form.errors.phone_number] : []" />
|
||||
<FieldError :errors="formErrors(form, 'phone_number')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="birth_date">Tanggal Lahir</FieldLabel>
|
||||
<DatePicker id="birth_date" v-model="form.birth_date"
|
||||
placeholder="Pilih tanggal lahir" />
|
||||
<FieldError :errors="form.errors.birth_date ? [form.errors.birth_date] : []" />
|
||||
<FieldError :errors="formErrors(form, 'birth_date')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>Jenis Kelamin</FieldLabel>
|
||||
@ -149,12 +150,12 @@ function submit() {
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<FieldError :errors="form.errors.gender ? [form.errors.gender] : []" />
|
||||
<FieldError :errors="formErrors(form, 'gender')" />
|
||||
</Field>
|
||||
<Field class="md:col-span-3">
|
||||
<FieldLabel for="address">Alamat</FieldLabel>
|
||||
<Textarea id="address" v-model="form.address" placeholder="Alamat lengkap" rows="3" />
|
||||
<FieldError :errors="form.errors.address ? [form.errors.address] : []" />
|
||||
<FieldError :errors="formErrors(form, 'address')" />
|
||||
</Field>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
@ -172,12 +173,12 @@ function submit() {
|
||||
<FieldLabel for="join_date" required>Tanggal Bergabung</FieldLabel>
|
||||
<DatePicker id="join_date" v-model="form.join_date"
|
||||
placeholder="Pilih tanggal bergabung" />
|
||||
<FieldError :errors="form.errors.join_date ? [form.errors.join_date] : []" />
|
||||
<FieldError :errors="formErrors(form, 'join_date')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="base_salary" required>Gaji Pokok</FieldLabel>
|
||||
<RupiahInput id="base_salary" v-model="form.base_salary" />
|
||||
<FieldError :errors="form.errors.base_salary ? [form.errors.base_salary] : []" />
|
||||
<FieldError :errors="formErrors(form, 'base_salary')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="employment_status" required>Status Kepegawaian</FieldLabel>
|
||||
@ -192,8 +193,7 @@ function submit() {
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FieldError
|
||||
:errors="form.errors.employment_status ? [form.errors.employment_status] : []" />
|
||||
<FieldError :errors="formErrors(form, 'employment_status')" />
|
||||
</Field>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
FieldLabel,
|
||||
FieldSet,
|
||||
} from '@/components/ui/field';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import type { LeaveRequestFormData, LeaveRequestListItem } from '@/types/leave-request';
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false });
|
||||
@ -100,12 +101,12 @@ function submit() {
|
||||
<Field>
|
||||
<FieldLabel for="leave-start-date" required>Tanggal Mulai</FieldLabel>
|
||||
<DatePicker id="leave-start-date" v-model="form.start_date" autofocus />
|
||||
<FieldError :errors="form.errors.start_date ? [form.errors.start_date] : []" />
|
||||
<FieldError :errors="formErrors(form, 'start_date')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="leave-end-date" required>Tanggal Selesai</FieldLabel>
|
||||
<DatePicker id="leave-end-date" v-model="form.end_date" />
|
||||
<FieldError :errors="form.errors.end_date ? [form.errors.end_date] : []" />
|
||||
<FieldError :errors="formErrors(form, 'end_date')" />
|
||||
</Field>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
FieldSet,
|
||||
} from '@/components/ui/field';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import type { LeaveRequestListItem } from '@/types/leave-request';
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false });
|
||||
@ -73,7 +74,7 @@ function submit() {
|
||||
<FieldLabel for="reject-reason" required>Alasan Penolakan</FieldLabel>
|
||||
<Textarea id="reject-reason" v-model="form.reason" placeholder="Tuliskan alasan penolakan"
|
||||
rows="3" autofocus />
|
||||
<FieldError :errors="form.errors.reason ? [form.errors.reason] : []" />
|
||||
<FieldError :errors="formErrors(form, 'reason')" />
|
||||
</Field>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
|
||||
@ -31,6 +31,7 @@ import {
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import type {
|
||||
CuttingMaterialCartItem,
|
||||
CuttingProductCatalogItem,
|
||||
@ -229,11 +230,11 @@ function submit() {
|
||||
<template>
|
||||
<div class="grid gap-4 xl:grid-cols-[1fr_400px]">
|
||||
<div class="space-y-4">
|
||||
<Card class="min-w-0">
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-base">Pilih Bahan Baku</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Card class="min-w-0">
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-base">Pilih Bahan Baku</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="space-y-4">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
@ -300,14 +301,14 @@ function submit() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card class="min-w-0">
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-base">Pilih Produk Hasil</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Card class="min-w-0">
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-base">Pilih Produk Hasil</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="space-y-4">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
@ -369,8 +370,8 @@ function submit() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card class="h-fit xl:sticky xl:top-4">
|
||||
@ -388,7 +389,7 @@ function submit() {
|
||||
<FieldLabel for="description">Keterangan</FieldLabel>
|
||||
<Textarea id="description" v-model="form.description"
|
||||
placeholder="Contoh: Cutting batch pagi" rows="2" />
|
||||
<FieldError :errors="form.errors.description ? [form.errors.description] : []" />
|
||||
<FieldError :errors="formErrors(form, 'description')" />
|
||||
</Field>
|
||||
|
||||
<div class="space-y-2">
|
||||
@ -410,7 +411,8 @@ function submit() {
|
||||
{{ item.raw_material_name }}
|
||||
</p>
|
||||
<p class="truncate text-xs text-muted-foreground">
|
||||
{{ item.variant }} · Stok {{ item.stock_input }} {{ item.unit_abbreviation }}
|
||||
{{ item.variant }} · Stok {{ item.stock_input }} {{
|
||||
item.unit_abbreviation }}
|
||||
</p>
|
||||
</div>
|
||||
<Button type="button" variant="ghost" size="icon"
|
||||
@ -474,13 +476,13 @@ function submit() {
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<Field>
|
||||
<FieldLabel class="text-xs">Hasil</FieldLabel>
|
||||
<Input v-model="item.cutting_result" type="number" min="1"
|
||||
class="h-8" @change="syncResultTotals(item)" />
|
||||
<Input v-model="item.cutting_result" type="number" min="1" class="h-8"
|
||||
@change="syncResultTotals(item)" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel class="text-xs">Gudang</FieldLabel>
|
||||
<Input v-model="item.warehouse_stock" type="number" min="0"
|
||||
class="h-8" @change="syncResultTotals(item)" />
|
||||
<Input v-model="item.warehouse_stock" type="number" min="0" class="h-8"
|
||||
@change="syncResultTotals(item)" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel class="text-xs">Reject</FieldLabel>
|
||||
|
||||
@ -33,9 +33,10 @@ import {
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { getFirstCoverImage } from '@/lib/catalog-cover';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import { formatRupiah, parseRupiah } from '@/lib/rupiah';
|
||||
import type { ProductPriceItem, ProductVariantItem } from '@/types/product';
|
||||
import type { EnumOption, OrderCartItem, OrderCatalogItem, SelectOption } from '@/types/order';
|
||||
import type { ProductPriceItem, ProductVariantItem } from '@/types/product';
|
||||
|
||||
const props = defineProps<{
|
||||
customers: SelectOption[];
|
||||
@ -251,25 +252,15 @@ function submit() {
|
||||
</div>
|
||||
|
||||
<div v-else class="columns-1 gap-4 sm:columns-2 xl:columns-3">
|
||||
<PosCatalogCard
|
||||
v-for="product in filteredCatalog"
|
||||
:key="product.id"
|
||||
:title="product.name"
|
||||
:cover-image="getFirstCoverImage(product.variants)"
|
||||
>
|
||||
<p
|
||||
v-if="!product.variants.length"
|
||||
class="px-3 py-4 text-sm text-muted-foreground"
|
||||
>
|
||||
<PosCatalogCard v-for="product in filteredCatalog" :key="product.id" :title="product.name"
|
||||
:cover-image="getFirstCoverImage(product.variants)">
|
||||
<p v-if="!product.variants.length" class="px-3 py-4 text-sm text-muted-foreground">
|
||||
Belum ada varian
|
||||
</p>
|
||||
<div
|
||||
v-for="variant in product.variants"
|
||||
:key="variant.id"
|
||||
<div v-for="variant in product.variants" :key="variant.id"
|
||||
class="flex items-center gap-2.5 px-3 py-2.5 transition-colors"
|
||||
:class="getVariantPrice(variant) ? 'cursor-pointer hover:bg-muted/30' : 'opacity-60'"
|
||||
@click="getVariantPrice(variant) && addToCart(product, variant)"
|
||||
>
|
||||
@click="getVariantPrice(variant) && addToCart(product, variant)">
|
||||
<PosCatalogVariantThumb :items="variant.images" />
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium">
|
||||
@ -284,14 +275,8 @@ function submit() {
|
||||
<span v-else>-</span>
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
class="shrink-0"
|
||||
:disabled="!getVariantPrice(variant)"
|
||||
@click.stop="addToCart(product, variant)"
|
||||
>
|
||||
<Button type="button" variant="outline" size="icon-sm" class="shrink-0"
|
||||
:disabled="!getVariantPrice(variant)" @click.stop="addToCart(product, variant)">
|
||||
<Plus class="size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
@ -326,7 +311,7 @@ function submit() {
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FieldError :errors="form.errors.channel ? [form.errors.channel] : []" />
|
||||
<FieldError :errors="formErrors(form, 'channel')" />
|
||||
</Field>
|
||||
|
||||
<Field v-if="isStoreChannel">
|
||||
@ -344,7 +329,7 @@ function submit() {
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FieldError :errors="form.errors.price_type ? [form.errors.price_type] : []" />
|
||||
<FieldError :errors="formErrors(form, 'price_type')" />
|
||||
</Field>
|
||||
|
||||
<Field v-else>
|
||||
@ -369,7 +354,7 @@ function submit() {
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FieldError :errors="form.errors.customer_id ? [form.errors.customer_id] : []" />
|
||||
<FieldError :errors="formErrors(form, 'customer_id')" />
|
||||
</Field>
|
||||
|
||||
<div v-if="cart.length === 0"
|
||||
@ -443,13 +428,12 @@ function submit() {
|
||||
<Field>
|
||||
<FieldLabel for="discount">Diskon</FieldLabel>
|
||||
<RupiahInput id="discount" v-model="form.discount" placeholder="0" />
|
||||
<FieldError :errors="form.errors.discount ? [form.errors.discount] : []" />
|
||||
<FieldError :errors="formErrors(form, 'discount')" />
|
||||
</Field>
|
||||
<Field v-if="isMarketplaceChannel">
|
||||
<FieldLabel for="marketplace_fee">Biaya Marketplace</FieldLabel>
|
||||
<RupiahInput id="marketplace_fee" v-model="form.marketplace_fee" placeholder="0" />
|
||||
<FieldError
|
||||
:errors="form.errors.marketplace_fee ? [form.errors.marketplace_fee] : []" />
|
||||
<FieldError :errors="formErrors(form, 'marketplace_fee')" />
|
||||
</Field>
|
||||
<div class="flex justify-between text-base font-semibold">
|
||||
<span>Net</span>
|
||||
@ -461,7 +445,7 @@ function submit() {
|
||||
<FieldLabel for="notes">Keterangan</FieldLabel>
|
||||
<Textarea id="notes" v-model="form.notes" placeholder="Contoh: Pesanan walk-in"
|
||||
rows="2" />
|
||||
<FieldError :errors="form.errors.notes ? [form.errors.notes] : []" />
|
||||
<FieldError :errors="formErrors(form, 'notes')" />
|
||||
</Field>
|
||||
|
||||
<Button type="submit" class="w-full" :disabled="form.processing || cart.length === 0">
|
||||
|
||||
@ -35,6 +35,7 @@ import {
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { getFirstCoverImage } from '@/lib/catalog-cover';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import { formatRupiah, parseRupiah } from '@/lib/rupiah';
|
||||
import type { MediaItem } from '@/types/media';
|
||||
import { appendRootPhotosToFormData, createMediaUploadState } from '@/types/media';
|
||||
@ -180,10 +181,6 @@ function buildFormData(): FormData {
|
||||
return formData;
|
||||
}
|
||||
|
||||
function formError(key: string): string | undefined {
|
||||
return (form.errors as Record<string, string>)[key];
|
||||
}
|
||||
|
||||
function submit() {
|
||||
if (cart.value.length === 0) {
|
||||
toast.error('Tambahkan minimal satu bahan baku ke keranjang.');
|
||||
@ -232,30 +229,20 @@ function submit() {
|
||||
</div>
|
||||
|
||||
<div v-else class="columns-1 gap-4 sm:columns-2 xl:columns-3">
|
||||
<PosCatalogCard
|
||||
v-for="rawMaterial in filteredCatalog"
|
||||
:key="rawMaterial.id"
|
||||
:title="rawMaterial.name"
|
||||
:cover-image="getFirstCoverImage(rawMaterial.prices)"
|
||||
>
|
||||
<PosCatalogCard v-for="rawMaterial in filteredCatalog" :key="rawMaterial.id"
|
||||
:title="rawMaterial.name" :cover-image="getFirstCoverImage(rawMaterial.prices)">
|
||||
<template #header-extra>
|
||||
<Badge variant="secondary" class="mt-1.5">
|
||||
{{ rawMaterial.unit_label }}
|
||||
</Badge>
|
||||
</template>
|
||||
|
||||
<p
|
||||
v-if="!rawMaterial.prices.length"
|
||||
class="px-3 py-4 text-sm text-muted-foreground"
|
||||
>
|
||||
<p v-if="!rawMaterial.prices.length" class="px-3 py-4 text-sm text-muted-foreground">
|
||||
Belum ada varian
|
||||
</p>
|
||||
<div
|
||||
v-for="price in rawMaterial.prices"
|
||||
:key="price.id"
|
||||
<div v-for="price in rawMaterial.prices" :key="price.id"
|
||||
class="flex cursor-pointer items-center gap-2.5 px-3 py-2.5 transition-colors hover:bg-muted/30"
|
||||
@click="addToCart(rawMaterial, price)"
|
||||
>
|
||||
@click="addToCart(rawMaterial, price)">
|
||||
<PosCatalogVariantThumb :items="price.images" />
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium">
|
||||
@ -265,13 +252,8 @@ function submit() {
|
||||
{{ price.price_formatted }}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
class="shrink-0"
|
||||
@click.stop="addToCart(rawMaterial, price)"
|
||||
>
|
||||
<Button type="button" variant="outline" size="icon-sm" class="shrink-0"
|
||||
@click.stop="addToCart(rawMaterial, price)">
|
||||
<Plus class="size-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
@ -306,7 +288,7 @@ function submit() {
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FieldError :errors="form.errors.supplier_id ? [form.errors.supplier_id] : []" />
|
||||
<FieldError :errors="formErrors(form, 'supplier_id')" />
|
||||
</Field>
|
||||
|
||||
<div v-if="cart.length === 0"
|
||||
@ -381,7 +363,7 @@ function submit() {
|
||||
<Field>
|
||||
<FieldLabel for="discount">Diskon</FieldLabel>
|
||||
<RupiahInput id="discount" v-model="form.discount" placeholder="0" />
|
||||
<FieldError :errors="form.errors.discount ? [form.errors.discount] : []" />
|
||||
<FieldError :errors="formErrors(form, 'discount')" />
|
||||
</Field>
|
||||
<div class="flex justify-between text-base font-semibold">
|
||||
<span>Total</span>
|
||||
@ -393,11 +375,11 @@ function submit() {
|
||||
<FieldLabel for="notes">Keterangan</FieldLabel>
|
||||
<Textarea id="notes" v-model="form.notes" placeholder="Contoh: Belanja batch 2"
|
||||
rows="2" />
|
||||
<FieldError :errors="form.errors.notes ? [form.errors.notes] : []" />
|
||||
<FieldError :errors="formErrors(form, 'notes')" />
|
||||
</Field>
|
||||
|
||||
<MediaImageUpload id="purchase-photos" v-model="form.media" label="Bukti Transaksi"
|
||||
:errors="formError('photos') ? [formError('photos')!] : []" />
|
||||
:errors="formErrors(form, 'photos')" />
|
||||
|
||||
<Button type="submit" class="w-full" :disabled="form.processing || cart.length === 0">
|
||||
<Save class="size-4" />
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
FieldSet,
|
||||
} from '@/components/ui/field';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import type { CategoryFormData, CategoryListItem } from '@/types/category';
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false });
|
||||
@ -96,7 +97,7 @@ function submit() {
|
||||
<FieldLabel for="category-name" required>Nama</FieldLabel>
|
||||
<Input id="category-name" v-model="form.name" type="text" placeholder="Contoh: Daster"
|
||||
autofocus />
|
||||
<FieldError :errors="form.errors.name ? [form.errors.name] : []" />
|
||||
<FieldError :errors="formErrors(form, 'name')" />
|
||||
</Field>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
|
||||
@ -21,7 +21,8 @@ import {
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { PhoneNumberInput } from '@/components/ui/phone-number-input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import type { CustomerFormData, CustomerListItem } from '@/types/customer';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import type { CustomerFormData, CustomerListItem } from '@/types/customers';
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false });
|
||||
|
||||
@ -102,19 +103,19 @@ function submit() {
|
||||
<FieldLabel for="customer-name" required>Nama</FieldLabel>
|
||||
<Input id="customer-name" v-model="form.name" type="text" placeholder="Nama customer"
|
||||
autofocus />
|
||||
<FieldError :errors="form.errors.name ? [form.errors.name] : []" />
|
||||
<FieldError :errors="formErrors(form, 'name')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="customer-phone-number" required>Nomor Telepon</FieldLabel>
|
||||
<PhoneNumberInput id="customer-phone-number" v-model="form.phone_number"
|
||||
placeholder="Nomor telepon customer" />
|
||||
<FieldError :errors="form.errors.phone_number ? [form.errors.phone_number] : []" />
|
||||
<FieldError :errors="formErrors(form, 'phone_number')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="customer-address" required>Alamat</FieldLabel>
|
||||
<Textarea id="customer-address" v-model="form.address" rows="3"
|
||||
placeholder="Alamat customer" />
|
||||
<FieldError :errors="form.errors.address ? [form.errors.address] : []" />
|
||||
<FieldError :errors="formErrors(form, 'address')" />
|
||||
</Field>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { RupiahInput } from '@/components/ui/rupiah-input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import { parseRupiah } from '@/lib/rupiah';
|
||||
import { appendMediaToFormData, createMediaUploadState } from '@/types/media';
|
||||
import {
|
||||
@ -282,7 +283,7 @@ function submit() {
|
||||
<FieldLabel for="name" required>Nama Produk</FieldLabel>
|
||||
<Input id="name" v-model="form.name" type="text"
|
||||
placeholder="Contoh: Midi Olla Dress" />
|
||||
<FieldError :errors="form.errors.name ? [form.errors.name] : []" />
|
||||
<FieldError :errors="formErrors(form, 'name')" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
@ -290,7 +291,7 @@ function submit() {
|
||||
<Textarea id="description" v-model="form.description"
|
||||
placeholder="Contoh: Hi Sobat, New arrival dari DST dengan Olla Dress. Yuks check detail produk: Detail: * Bahan Cey Flow Bordir * Ld 120 cm * PB 110 cm * Bisa jadi dress..."
|
||||
rows="4" />
|
||||
<FieldError :errors="form.errors.description ? [form.errors.description] : []" />
|
||||
<FieldError :errors="formErrors(form, 'description')" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import { parseRupiah } from '@/lib/rupiah';
|
||||
import { appendMediaToFormData, createMediaUploadState } from '@/types/media';
|
||||
import type {
|
||||
@ -228,7 +229,7 @@ function submit() {
|
||||
<FieldLabel for="name" required>Nama Bahan Baku</FieldLabel>
|
||||
<Input id="name" v-model="form.name" type="text"
|
||||
placeholder="Masukkan nama bahan baku" />
|
||||
<FieldError :errors="form.errors.name ? [form.errors.name] : []" />
|
||||
<FieldError :errors="formErrors(form, 'name')" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
@ -243,7 +244,7 @@ function submit() {
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FieldError :errors="form.errors.unit ? [form.errors.unit] : []" />
|
||||
<FieldError :errors="formErrors(form, 'unit')" />
|
||||
</Field>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
@ -334,7 +335,7 @@ function submit() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<FieldError :errors="formError('prices') ? [formError('prices')!] : []" />
|
||||
<FieldError :errors="formErrors(form, 'prices')" />
|
||||
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<Button type="button" variant="outline" @click="addPrice">
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { PhoneNumberInput } from '@/components/ui/phone-number-input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import type { SupplierFormData, SupplierListItem } from '@/types/supplier';
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false });
|
||||
@ -102,19 +103,19 @@ function submit() {
|
||||
<FieldLabel for="supplier-name" required>Nama</FieldLabel>
|
||||
<Input id="supplier-name" v-model="form.name" type="text" placeholder="Nama supplier"
|
||||
autofocus />
|
||||
<FieldError :errors="form.errors.name ? [form.errors.name] : []" />
|
||||
<FieldError :errors="formErrors(form, 'name')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="supplier-phone-number" required>Nomor Telepon</FieldLabel>
|
||||
<PhoneNumberInput id="supplier-phone-number" v-model="form.phone_number"
|
||||
placeholder="Nomor telepon supplier" />
|
||||
<FieldError :errors="form.errors.phone_number ? [form.errors.phone_number] : []" />
|
||||
<FieldError :errors="formErrors(form, 'phone_number')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="supplier-address" required>Alamat</FieldLabel>
|
||||
<Textarea id="supplier-address" v-model="form.address" rows="3"
|
||||
placeholder="Alamat supplier" />
|
||||
<FieldError :errors="form.errors.address ? [form.errors.address] : []" />
|
||||
<FieldError :errors="formErrors(form, 'address')" />
|
||||
</Field>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
|
||||
10
resources/js/lib/form.ts
Normal file
10
resources/js/lib/form.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import type { InertiaForm } from '@inertiajs/vue3';
|
||||
|
||||
export function formErrors<T extends Record<string, unknown>>(
|
||||
form: InertiaForm<T>,
|
||||
key: keyof T | string,
|
||||
): string[] {
|
||||
const error = form.errors[key as keyof typeof form.errors];
|
||||
|
||||
return error ? [error] : [];
|
||||
}
|
||||
@ -7,14 +7,13 @@ import { Card, CardContent } from '@/components/ui/card';
|
||||
import {
|
||||
Field,
|
||||
FieldError,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldSet,
|
||||
FieldGroup, FieldSet
|
||||
} from '@/components/ui/field';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { applyAppearance } from '@/composables/useAppearance';
|
||||
import AccountLayout from '@/layouts/AccountLayout.vue';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { AppearanceFormData, AppearanceMode } from '@/types/account';
|
||||
|
||||
@ -102,7 +101,7 @@ function submit() {
|
||||
</div>
|
||||
</Label>
|
||||
</RadioGroup>
|
||||
<FieldError :errors="form.errors.appearance ? [form.errors.appearance] : []" />
|
||||
<FieldError :errors="formErrors(form, 'appearance')" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
} from '@/components/ui/field';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import AccountLayout from '@/layouts/AccountLayout.vue';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import type { PasswordFormData } from '@/types/account';
|
||||
|
||||
const form = useForm<PasswordFormData>({
|
||||
@ -50,8 +51,7 @@ function submit() {
|
||||
</FieldLabel>
|
||||
<Input id="current_password" v-model="form.current_password" type="password"
|
||||
placeholder="********" autocomplete="current-password" />
|
||||
<FieldError
|
||||
:errors="form.errors.current_password ? [form.errors.current_password] : []" />
|
||||
<FieldError :errors="formErrors(form, 'current_password')" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
@ -60,7 +60,7 @@ function submit() {
|
||||
</FieldLabel>
|
||||
<Input id="password" v-model="form.password" type="password" placeholder="********"
|
||||
autocomplete="new-password" />
|
||||
<FieldError :errors="form.errors.password ? [form.errors.password] : []" />
|
||||
<FieldError :errors="formErrors(form, 'password')" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
@ -69,8 +69,7 @@ function submit() {
|
||||
</FieldLabel>
|
||||
<Input id="password_confirmation" v-model="form.password_confirmation"
|
||||
type="password" placeholder="********" autocomplete="new-password" />
|
||||
<FieldError
|
||||
:errors="form.errors.password_confirmation ? [form.errors.password_confirmation] : []" />
|
||||
<FieldError :errors="formErrors(form, 'password_confirmation')" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
|
||||
@ -17,6 +17,7 @@ import { PhoneNumberInput } from '@/components/ui/phone-number-input';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import AccountLayout from '@/layouts/AccountLayout.vue';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import type { EnumOption, ProfileFormData, ProfileUser } from '@/types/account';
|
||||
|
||||
const props = defineProps<{
|
||||
@ -60,7 +61,7 @@ function submit() {
|
||||
</FieldLabel>
|
||||
<Input id="email" v-model="form.email" type="email"
|
||||
placeholder="nama@perusahaan.com" />
|
||||
<FieldError :errors="form.errors.email ? [form.errors.email] : []" />
|
||||
<FieldError :errors="formErrors(form, 'email')" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
@ -68,7 +69,7 @@ function submit() {
|
||||
Username
|
||||
</FieldLabel>
|
||||
<Input id="username" v-model="form.username" type="text" placeholder="username" />
|
||||
<FieldError :errors="form.errors.username ? [form.errors.username] : []" />
|
||||
<FieldError :errors="formErrors(form, 'username')" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
@ -77,7 +78,7 @@ function submit() {
|
||||
</FieldLabel>
|
||||
<Input id="full_name" v-model="form.full_name" type="text"
|
||||
placeholder="Nama lengkap" />
|
||||
<FieldError :errors="form.errors.full_name ? [form.errors.full_name] : []" />
|
||||
<FieldError :errors="formErrors(form, 'full_name')" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
@ -85,7 +86,7 @@ function submit() {
|
||||
Nomor Telepon
|
||||
</FieldLabel>
|
||||
<PhoneNumberInput id="phone_number" v-model="form.phone_number" />
|
||||
<FieldError :errors="form.errors.phone_number ? [form.errors.phone_number] : []" />
|
||||
<FieldError :errors="formErrors(form, 'phone_number')" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
@ -93,7 +94,7 @@ function submit() {
|
||||
Tanggal Lahir
|
||||
</FieldLabel>
|
||||
<DatePicker id="birth_date" v-model="form.birth_date" />
|
||||
<FieldError :errors="form.errors.birth_date ? [form.errors.birth_date] : []" />
|
||||
<FieldError :errors="formErrors(form, 'birth_date')" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
@ -109,7 +110,7 @@ function submit() {
|
||||
</label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<FieldError :errors="form.errors.gender ? [form.errors.gender] : []" />
|
||||
<FieldError :errors="formErrors(form, 'gender')" />
|
||||
</Field>
|
||||
|
||||
<Field class="sm:col-span-3">
|
||||
@ -118,7 +119,7 @@ function submit() {
|
||||
</FieldLabel>
|
||||
<Textarea id="address" v-model="form.address" placeholder="Alamat lengkap"
|
||||
rows="3" />
|
||||
<FieldError :errors="form.errors.address ? [form.errors.address] : []" />
|
||||
<FieldError :errors="formErrors(form, 'address')" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
|
||||
@ -6,7 +6,6 @@ export type CategoryListItem = {
|
||||
|
||||
export type CategoryFormData = {
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type CategoryFilters = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user