diff --git a/resources/js/components/input-error.tsx b/resources/js/components/input-error.tsx index f323cee..278672c 100644 --- a/resources/js/components/input-error.tsx +++ b/resources/js/components/input-error.tsx @@ -1,17 +1,64 @@ import type { HTMLAttributes } from 'react'; import { cn } from '@/lib/utils'; +import { useMemo } from 'react'; + +interface InputErrorProps extends HTMLAttributes { + message?: string; + label?: string; +} export default function InputError({ message, + label, className = '', ...props -}: HTMLAttributes & { message?: string }) { - return message ? ( +}: InputErrorProps) { + const formattedError = useMemo(() => { + if (!message) return null; + if (!label) return message; + + // Preserve all-caps labels (like NIK), otherwise capitalize first letter and lowercase the rest + const isAllOptionsCaps = label === label.toUpperCase() && label.length > 1; + const formattedLabel = isAllOptionsCaps + ? label + : label.charAt(0).toUpperCase() + label.slice(1).toLowerCase(); + + const words = message.split(' '); + + // List of common Indonesian and English validation verbs/connectors that follow the attribute + const verbs = [ + 'wajib', 'harus', 'berupa', 'adalah', 'minimal', 'maksimal', 'tidak', 'kurang', 'lebih', 'antara', 'sudah', + 'is', 'must', 'field', 'has', 'was', 'should', 'cannot', 'required', 'invalid' + ]; + + // Find the first occurrence of a verb + let verbIndex = -1; + for (let i = 0; i < words.length; i++) { + if (verbs.includes(words[i].toLowerCase())) { + verbIndex = i; + break; + } + } + + if (verbIndex !== -1) { + // Replace everything before the verb with the label + return `${formattedLabel} ${words.slice(verbIndex).join(' ')}`; + } + + // Fallback to replacing only the first word if no verb found + if (words.length > 0) { + return `${formattedLabel} ${words.slice(1).join(' ')}`; + } + + return message; + }, [message, label]); + + return formattedError ? (

- {message} + {formattedError}

) : null; } diff --git a/resources/js/components/ui/field.tsx b/resources/js/components/ui/field.tsx index 098c256..47c713c 100644 --- a/resources/js/components/ui/field.tsx +++ b/resources/js/components/ui/field.tsx @@ -184,37 +184,59 @@ function FieldSeparator({ function FieldError({ className, children, - errors, + error, + label, ...props }: React.ComponentProps<"div"> & { - errors?: Array<{ message?: string } | undefined> + error?: string | string[] + label?: string }) { const content = useMemo(() => { if (children) { return children } - if (!errors?.length) { + const rawError = Array.isArray(error) ? error[0] : error + + if (!rawError) { return null } - const uniqueErrors = [ - ...new Map(errors.map((error) => [error?.message, error])).values(), - ] + if (label) { + const isAllUppercase = label === label.toUpperCase() && label.length > 1 + const formattedLabel = isAllUppercase + ? label + : label.charAt(0).toUpperCase() + label.slice(1).toLowerCase() + + const words = rawError.split(" ") + + // List of common Indonesian and English validation verbs/connectors that follow the attribute + const verbs = [ + "wajib", "harus", "berupa", "adalah", "minimal", "maksimal", "tidak", "kurang", "lebih", "antara", "sudah", + "is", "must", "field", "has", "was", "should", "cannot", "required", "invalid" + ] + + // Find the first occurrence of a verb + let verbIndex = -1 + for (let i = 0; i < words.length; i++) { + if (verbs.includes(words[i].toLowerCase())) { + verbIndex = i + break + } + } + + if (verbIndex !== -1) { + // Replace everything before the verb with the label + // If the first word was "The" or "Isian", we replace them too + return `${formattedLabel} ${words.slice(verbIndex).join(" ")}` + } - if (uniqueErrors?.length == 1) { - return uniqueErrors[0]?.message + // Fallback to replacing only the first word if no verb found + return `${formattedLabel} ${words.slice(1).join(" ")}` } - return ( - - ) - }, [children, errors]) + return rawError + }, [children, error, label]) if (!content) { return null diff --git a/resources/js/pages/admin/finance/expense/partials/expense-form-modal.tsx b/resources/js/pages/admin/finance/expense/partials/expense-form-modal.tsx index 7504bc3..bd566ec 100644 --- a/resources/js/pages/admin/finance/expense/partials/expense-form-modal.tsx +++ b/resources/js/pages/admin/finance/expense/partials/expense-form-modal.tsx @@ -6,7 +6,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog" -import { Field, FieldGroup } from "@/components/ui/field" +import { Field, FieldError, FieldGroup } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Expense } from '@/types'; @@ -122,7 +122,7 @@ export function ExpenseFormModal({ isOpen, onClose, expense }: ExpenseFormModalP placeholder='Contoh: Bayar Listrik' maxLength={100} /> - {errors.name &&

{errors.name}

} + @@ -140,7 +140,7 @@ export function ExpenseFormModal({ isOpen, onClose, expense }: ExpenseFormModalP placeholder="Rp 0" autoComplete='off' /> - {errors.amount &&

{errors.amount}

} +
@@ -179,7 +179,7 @@ export function ExpenseFormModal({ isOpen, onClose, expense }: ExpenseFormModalP onChange={onImageChange} /> - {errors.image &&

{errors.image}

} +
diff --git a/resources/js/pages/admin/manage/order/create.tsx b/resources/js/pages/admin/manage/order/create.tsx index dc9f8f0..06b15d2 100644 --- a/resources/js/pages/admin/manage/order/create.tsx +++ b/resources/js/pages/admin/manage/order/create.tsx @@ -1,7 +1,7 @@ import { Head, Link, useForm, router } from '@inertiajs/react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { Field } from "@/components/ui/field"; +import { Field, FieldError } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import * as orderRoutes from '@/routes/order'; @@ -268,7 +268,7 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha setData('customer_name', e.target.value)} placeholder="Nama Pelanggan" /> - {errors.customer_name &&

{errors.customer_name}

} +
@@ -282,7 +282,7 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha ))} - {errors.order_channel &&

{errors.order_channel}

} +
@@ -299,7 +299,7 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha ))} - {errors.payment_method &&

{errors.payment_method}

} + @@ -313,7 +313,7 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha ))} - {errors.order_status &&

{errors.order_status}

} +
@@ -338,7 +338,7 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha placeholder="Rp 0" autoComplete='off' /> - {errors.discount &&

{errors.discount}

} + diff --git a/resources/js/pages/admin/manage/order/edit.tsx b/resources/js/pages/admin/manage/order/edit.tsx index fae05bc..0a04f3e 100644 --- a/resources/js/pages/admin/manage/order/edit.tsx +++ b/resources/js/pages/admin/manage/order/edit.tsx @@ -1,7 +1,7 @@ import { Head, Link, useForm, router } from '@inertiajs/react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { Field } from "@/components/ui/field"; +import { Field, FieldError } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import * as orderRoutes from '@/routes/order'; @@ -270,7 +270,7 @@ export default function OrderEdit({ order, products, orderStatus, orderChannels, setData('customer_name', e.target.value)} /> - {errors.customer_name &&

{errors.customer_name}

} +
@@ -282,7 +282,7 @@ export default function OrderEdit({ order, products, orderStatus, orderChannels, ))} - {errors.order_channel &&

{errors.order_channel}

} +
@@ -297,7 +297,7 @@ export default function OrderEdit({ order, products, orderStatus, orderChannels, ))} - {errors.payment_method &&

{errors.payment_method}

} + @@ -309,7 +309,7 @@ export default function OrderEdit({ order, products, orderStatus, orderChannels, ))} - {errors.order_status &&

{errors.order_status}

} +
@@ -335,7 +335,7 @@ export default function OrderEdit({ order, products, orderStatus, orderChannels, autoComplete='off' /> - {errors.discount &&

{errors.discount}

} + diff --git a/resources/js/pages/admin/manage/purchase/create.tsx b/resources/js/pages/admin/manage/purchase/create.tsx index 934773a..4ab7d15 100644 --- a/resources/js/pages/admin/manage/purchase/create.tsx +++ b/resources/js/pages/admin/manage/purchase/create.tsx @@ -1,7 +1,7 @@ import { Head, Link, useForm, router } from '@inertiajs/react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { Field } from "@/components/ui/field"; +import { Field, FieldError } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import purchaseRoutes from '@/routes/purchase'; @@ -272,11 +272,13 @@ export default function PurchaseCreate({ products, cartItems }: { products: Prod /> + setData('note', e.target.value)} placeholder="...." /> +
@@ -577,6 +579,7 @@ export default function PurchaseCreate({ products, cartItems }: { products: Prod setData('note', e.target.value)} placeholder="...." /> +
@@ -752,6 +755,7 @@ export default function PurchaseCreate({ products, cartItems }: { products: Prod setData('note', e.target.value)} placeholder="...." /> +
diff --git a/resources/js/pages/admin/manage/purchase/edit.tsx b/resources/js/pages/admin/manage/purchase/edit.tsx index 441e6d6..bf0b54c 100644 --- a/resources/js/pages/admin/manage/purchase/edit.tsx +++ b/resources/js/pages/admin/manage/purchase/edit.tsx @@ -1,7 +1,7 @@ import { Head, Link, useForm, router } from '@inertiajs/react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { Field } from "@/components/ui/field"; +import { Field, FieldError } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import purchaseRoutes from '@/routes/purchase'; @@ -276,11 +276,13 @@ export default function PurchaseEdit({ purchase, products }: { purchase: Purchas /> + setData('note', e.target.value)} placeholder="...." /> +
@@ -576,11 +578,13 @@ export default function PurchaseEdit({ purchase, products }: { purchase: Purchas /> + setData('note', e.target.value)} placeholder="...." /> +
@@ -756,6 +760,7 @@ export default function PurchaseEdit({ purchase, products }: { purchase: Purchas setData('note', e.target.value)} placeholder="...." /> +
diff --git a/resources/js/pages/admin/master/category/partials/category-form-modal.tsx b/resources/js/pages/admin/master/category/partials/category-form-modal.tsx index d3f3b1a..a314a67 100644 --- a/resources/js/pages/admin/master/category/partials/category-form-modal.tsx +++ b/resources/js/pages/admin/master/category/partials/category-form-modal.tsx @@ -6,7 +6,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog" -import { Field, FieldGroup } from "@/components/ui/field" +import { Field, FieldError, FieldGroup } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Category } from '@/types'; @@ -85,7 +85,7 @@ export function CategoryFormModal({ isOpen, onClose, category }: CategoryFormMod placeholder='Contoh: Gamis' maxLength={50} /> - {errors.name &&

{errors.name}

} + diff --git a/resources/js/pages/admin/master/product/create.tsx b/resources/js/pages/admin/master/product/create.tsx index ee99f86..84e3bd0 100644 --- a/resources/js/pages/admin/master/product/create.tsx +++ b/resources/js/pages/admin/master/product/create.tsx @@ -2,7 +2,7 @@ import { Head, Link } from '@inertiajs/react'; import { useForm } from '@inertiajs/react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { Field } from "@/components/ui/field" +import { Field, FieldError } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import productRoutes from '@/routes/product'; @@ -143,7 +143,7 @@ export default function ProductCreate({ categories }: { categories: Category[] } placeholder='Contoh: Gamis Wanita' maxLength={100} /> - {errors.name &&

{errors.name}

} + @@ -185,7 +185,7 @@ export default function ProductCreate({ categories }: { categories: Category[] } - {errors.category_ids &&

{errors.category_ids}

} +
@@ -194,7 +194,7 @@ export default function ProductCreate({ categories }: { categories: Category[] } value={data.description} onChange={(val) => setData('description', val)} /> - {errors.description &&

{errors.description}

} +
@@ -223,7 +223,7 @@ export default function ProductCreate({ categories }: { categories: Category[] } placeholder="Rp 0" autoComplete='off' /> - {errors['prices.purchase'] &&

{errors['prices.purchase']}

} + @@ -244,7 +244,7 @@ export default function ProductCreate({ categories }: { categories: Category[] } placeholder="Rp 0" autoComplete='off' /> - {errors['prices.distributor'] &&

{errors['prices.distributor']}

} +
@@ -265,7 +265,7 @@ export default function ProductCreate({ categories }: { categories: Category[] } placeholder="Rp 0" autoComplete='off' /> - {errors['prices.agent'] &&

{errors['prices.agent']}

} +
@@ -286,7 +286,7 @@ export default function ProductCreate({ categories }: { categories: Category[] } placeholder="Rp 0" autoComplete='off' /> - {errors['prices.reseller'] &&

{errors['prices.reseller']}

} +
@@ -307,7 +307,7 @@ export default function ProductCreate({ categories }: { categories: Category[] } placeholder="Rp 0" autoComplete='off' /> - {errors['prices.retail'] &&

{errors['prices.retail']}

} +
@@ -353,7 +353,7 @@ export default function ProductCreate({ categories }: { categories: Category[] } className="hidden" onChange={handleThumbnailChange} /> - {errors.thumbnail &&

{errors.thumbnail}

} + @@ -397,7 +397,7 @@ export default function ProductCreate({ categories }: { categories: Category[] } className="hidden" onChange={handleImagesChange} /> - {errors['images'] &&

{errors['images']}

} + diff --git a/resources/js/pages/admin/master/product/edit.tsx b/resources/js/pages/admin/master/product/edit.tsx index 42a0d2e..53d5ec8 100644 --- a/resources/js/pages/admin/master/product/edit.tsx +++ b/resources/js/pages/admin/master/product/edit.tsx @@ -3,7 +3,7 @@ import type { Product, ProductPrice } from '@/types'; import { Category } from '@/types/category'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { Field } from "@/components/ui/field" +import { Field, FieldError } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { toast } from 'sonner'; @@ -194,7 +194,7 @@ export default function ProductEdit({ product, categories }: { product: Product, placeholder='Contoh: Gamis Wanita' maxLength={100} /> - {errors.name &&

{errors.name}

} + @@ -209,7 +209,7 @@ export default function ProductEdit({ product, categories }: { product: Product, placeholder="0" autoComplete='off' /> - {errors.stock &&

{errors.stock}

} +
@@ -252,7 +252,7 @@ export default function ProductEdit({ product, categories }: { product: Product, - {errors.category_ids &&

{errors.category_ids}

} + @@ -261,7 +261,7 @@ export default function ProductEdit({ product, categories }: { product: Product, value={data.description} onChange={(val) => setData('description', val)} /> - {errors.description &&

{errors.description}

} +
@@ -290,7 +290,7 @@ export default function ProductEdit({ product, categories }: { product: Product, placeholder="Rp 0" autoComplete='off' /> - {errors['prices.purchase'] &&

{errors['prices.purchase']}

} + @@ -311,7 +311,7 @@ export default function ProductEdit({ product, categories }: { product: Product, placeholder="Rp 0" autoComplete='off' /> - {errors['prices.distributor'] &&

{errors['prices.distributor']}

} +
@@ -332,7 +332,7 @@ export default function ProductEdit({ product, categories }: { product: Product, placeholder="Rp 0" autoComplete='off' /> - {errors['prices.agent'] &&

{errors['prices.agent']}

} +
@@ -353,7 +353,7 @@ export default function ProductEdit({ product, categories }: { product: Product, placeholder="Rp 0" autoComplete='off' /> - {errors['prices.reseller'] &&

{errors['prices.reseller']}

} +
@@ -374,7 +374,7 @@ export default function ProductEdit({ product, categories }: { product: Product, placeholder="Rp 0" autoComplete='off' /> - {errors['prices.retail'] &&

{errors['prices.retail']}

} +
@@ -420,7 +420,7 @@ export default function ProductEdit({ product, categories }: { product: Product, className="hidden" onChange={handleThumbnailChange} /> - {errors.thumbnail &&

{errors.thumbnail}

} + @@ -464,7 +464,7 @@ export default function ProductEdit({ product, categories }: { product: Product, className="hidden" onChange={handleImagesChange} /> - {errors['images'] &&

{errors['images']}

} + diff --git a/resources/js/pages/admin/master/user/create.tsx b/resources/js/pages/admin/master/user/create.tsx index d0f0992..498e3d9 100644 --- a/resources/js/pages/admin/master/user/create.tsx +++ b/resources/js/pages/admin/master/user/create.tsx @@ -2,7 +2,7 @@ import { Head, Link } from '@inertiajs/react'; import { useForm } from '@inertiajs/react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { Field } from "@/components/ui/field" +import { Field, FieldError } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import userRoutes from '@/routes/user'; @@ -77,7 +77,7 @@ export default function UserCreate() { autoComplete='off' placeholder='Contoh: John Doe' /> - {errors.full_name &&

{errors.full_name}

} + @@ -91,7 +91,7 @@ export default function UserCreate() { placeholder='Contoh: 3213051307900001' maxLength={16} /> - {errors.nik &&

{errors.nik}

} +
@@ -104,7 +104,7 @@ export default function UserCreate() { autoComplete='off' placeholder='Contoh: 08123456789' /> - {errors.phone_number &&

{errors.phone_number}

} +
@@ -122,7 +122,7 @@ export default function UserCreate() { placeholder="Rp 0" autoComplete='off' /> - {errors.base_salary &&

{errors.base_salary}

} +
@@ -135,7 +135,7 @@ export default function UserCreate() { autoComplete='off' placeholder='Contoh: Jakarta' /> - {errors.birth_place &&

{errors.birth_place}

} +
@@ -175,14 +175,14 @@ export default function UserCreate() { /> - {errors.birth_date &&

{errors.birth_date}

} +