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