refactor: add autoComplete attribute to input fields across order, purchase, and product forms for improved user experience

This commit is contained in:
Yoga Pangestu 2026-04-24 23:57:35 +07:00
parent f383f3bd00
commit 9d0fc6d15e
10 changed files with 117 additions and 135 deletions

View File

@ -275,7 +275,7 @@ return;
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<Field> <Field>
<Label htmlFor='customer_name' className="text-xs text-muted-foreground" required>Pelanggan</Label> <Label htmlFor='customer_name' className="text-xs text-muted-foreground" required>Pelanggan</Label>
<Input id="customer_name" className="h-8 text-xs" value={data.customer_name} onChange={e => setData('customer_name', e.target.value)} placeholder="Nama Pelanggan" /> <Input id="customer_name" className="h-8 text-xs" value={data.customer_name} onChange={e => setData('customer_name', e.target.value)} autoComplete="off" placeholder="Nama Pelanggan" />
<FieldError error={errors.customer_name} label="Pelanggan" className="text-xs" /> <FieldError error={errors.customer_name} label="Pelanggan" className="text-xs" />
</Field> </Field>
<Field> <Field>

View File

@ -285,7 +285,7 @@ return;
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<Field> <Field>
<Label htmlFor='customer_name' className="text-xs text-muted-foreground" required>Pelanggan</Label> <Label htmlFor='customer_name' className="text-xs text-muted-foreground" required>Pelanggan</Label>
<Input id='customer_name' className="h-8 text-xs" value={data.customer_name} onChange={e => setData('customer_name', e.target.value)} /> <Input id='customer_name' className="h-8 text-xs" value={data.customer_name} autoComplete="off" onChange={e => setData('customer_name', e.target.value)} />
<FieldError error={errors.customer_name} label="Pelanggan" className="text-xs" /> <FieldError error={errors.customer_name} label="Pelanggan" className="text-xs" />
</Field> </Field>
<Field> <Field>

View File

@ -596,7 +596,7 @@ return;
<Field> <Field>
<Label htmlFor="note" className="text-xs text-muted-foreground">Catatan</Label> <Label htmlFor="note" className="text-xs text-muted-foreground">Catatan</Label>
<Input id="note" className="h-8 text-xs" value={data.note} onChange={e => setData('note', e.target.value)} placeholder="...." /> <Input id="note" className="h-8 text-xs" value={data.note} onChange={e => setData('note', e.target.value)} autoComplete='off' placeholder="...." />
<FieldError error={errors.note} label="Catatan" className="text-xs" /> <FieldError error={errors.note} label="Catatan" className="text-xs" />
</Field> </Field>

View File

@ -1,8 +1,3 @@
import { Head, Link, useForm } from '@inertiajs/react';
import { format } from 'date-fns';
import { Plus, Trash2, CalendarIcon, Minus, ShoppingCart, Search, ImagePlus, X, Loader, Save, ArrowLeft } from 'lucide-react';
import React, { useMemo, useState } from 'react';
import { toast } from 'sonner';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar'; import { Calendar } from '@/components/ui/calendar';
@ -10,9 +5,9 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogFooter,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogFooter,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from '@/components/ui/empty'; import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from '@/components/ui/empty';
import { Field, FieldError } from "@/components/ui/field"; import { Field, FieldError } from "@/components/ui/field";
@ -29,14 +24,19 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { import {
Sheet, Sheet,
SheetClose,
SheetContent, SheetContent,
SheetTrigger, SheetTrigger,
SheetClose,
} from "@/components/ui/sheet"; } from "@/components/ui/sheet";
import { formatCurrency } from '@/lib/formatters';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { formatCurrency, formatNumber } from '@/lib/formatters';
import purchaseRoutes from '@/routes/purchase'; import purchaseRoutes from '@/routes/purchase';
import type { Product, ProductPrice, Purchase } from '@/types'; import type { Product, ProductPrice, Purchase } from '@/types';
import { Head, Link, useForm } from '@inertiajs/react';
import { format } from 'date-fns';
import { ArrowLeft, CalendarIcon, ImagePlus, Loader, Minus, Plus, Save, Search, ShoppingCart, Trash2, X } from 'lucide-react';
import React, { useMemo, useState } from 'react';
import { toast } from 'sonner';
type CartItem = { product_id: number; quantity: number; unit_price: number; product: Product }; type CartItem = { product_id: number; quantity: number; unit_price: number; product: Product };
@ -104,8 +104,8 @@ export default function PurchaseEdit({ purchase, products }: { purchase: Purchas
const existingIndex = data.items.findIndex(i => i.product_id === product.id); const existingIndex = data.items.findIndex(i => i.product_id === product.id);
if (existingIndex === -1) { if (existingIndex === -1) {
return; return;
} }
const newItems = [...data.items]; const newItems = [...data.items];
@ -130,8 +130,8 @@ return;
const updateCartQuantity = (productId: number, quantity: number) => { const updateCartQuantity = (productId: number, quantity: number) => {
if (quantity < 1) { if (quantity < 1) {
return; return;
} }
const newItems = [...data.items]; const newItems = [...data.items];
const index = newItems.findIndex(i => i.product_id === productId); const index = newItems.findIndex(i => i.product_id === productId);
@ -296,7 +296,7 @@ return;
<Field> <Field>
<Label htmlFor='note' className="text-xs text-muted-foreground">Catatan</Label> <Label htmlFor='note' className="text-xs text-muted-foreground">Catatan</Label>
<Input id='note' className="h-8 text-xs" value={data.note} onChange={e => setData('note', e.target.value)} placeholder="...." /> <Input id='note' className="h-8 text-xs" value={data.note} onChange={e => setData('note', e.target.value)} autoComplete='off' placeholder="...." />
<FieldError error={errors.note} label="Catatan" className="text-xs" /> <FieldError error={errors.note} label="Catatan" className="text-xs" />
</Field> </Field>
@ -449,8 +449,8 @@ return;
)} )}
onClick={(e) => { onClick={(e) => {
if (!cartItem) { if (!cartItem) {
return; return;
} }
openQtyDialog(cartItem, e); openQtyDialog(cartItem, e);
}} }}
@ -810,10 +810,10 @@ return;
</div> </div>
{/* Quantity Input Dialog */} {/* Quantity Input Dialog */}
<Dialog open={qtyDialogIndex !== null} onOpenChange={(open) => { <Dialog open={qtyDialogIndex !== null} onOpenChange={(open) => {
if (!open) { if (!open) {
setQtyDialogIndex(null); setQtyDialogIndex(null);
} }
}}> }}>
<DialogContent className="max-w-xs"> <DialogContent className="max-w-xs">
<DialogHeader> <DialogHeader>
<DialogTitle>Ubah Jumlah</DialogTitle> <DialogTitle>Ubah Jumlah</DialogTitle>
@ -828,10 +828,10 @@ setQtyDialogIndex(null);
value={qtyInputValue} value={qtyInputValue}
onChange={e => setQtyInputValue(e.target.value)} onChange={e => setQtyInputValue(e.target.value)}
onKeyDown={e => { onKeyDown={e => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
confirmQty(); confirmQty();
} }
}} }}
className="text-lg font-bold" className="text-lg font-bold"
autoFocus autoFocus
/> />

View File

@ -1,29 +1,28 @@
import { Head, Link } from '@inertiajs/react'; import { SimpleEditor } from '@/components/tiptap-templates/simple/simple-editor';
import { useForm } from '@inertiajs/react';
import { ImagePlus, X, Upload, ArrowLeft, Save, Loader } from 'lucide-react';
import React, { useRef, useState } from 'react';
import { NumericFormat } from 'react-number-format';
import { toast } from 'sonner';
import { SimpleEditor } from '@/components/tiptap-templates/simple/simple-editor'
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { import {
Combobox, Combobox,
ComboboxChip,
ComboboxChips,
ComboboxChipsInput,
ComboboxContent, ComboboxContent,
ComboboxEmpty, ComboboxEmpty,
ComboboxItem, ComboboxItem,
ComboboxList, ComboboxList,
ComboboxChips,
ComboboxChip,
ComboboxChipsInput,
ComboboxValue, ComboboxValue,
useComboboxAnchor, useComboboxAnchor,
} from "@/components/ui/combobox" } from "@/components/ui/combobox";
import { Field, FieldError } from "@/components/ui/field" import { Field, FieldError } from "@/components/ui/field";
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label";
import productRoutes from '@/routes/product'; import productRoutes from '@/routes/product';
import type { Category } from '@/types/category'; import type { Category } from '@/types/category';
import { Head, Link, useForm } from '@inertiajs/react';
import { ArrowLeft, ImagePlus, Loader, Save, Upload, X } from 'lucide-react';
import React, { useRef, useState } from 'react';
import { NumericFormat } from 'react-number-format';
import { toast } from 'sonner';
export default function ProductCreate({ categories }: { categories: Category[] }) { export default function ProductCreate({ categories }: { categories: Category[] }) {
const { data, setData, post, processing, errors } = useForm<{ const { data, setData, post, processing, errors } = useForm<{
@ -65,8 +64,8 @@ export default function ProductCreate({ categories }: { categories: Category[] }
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (!file) { if (!file) {
return; return;
} }
setData('thumbnail', file); setData('thumbnail', file);
const reader = new FileReader(); const reader = new FileReader();
@ -79,16 +78,16 @@ return;
setThumbnailPreview(null); setThumbnailPreview(null);
if (thumbnailInputRef.current) { if (thumbnailInputRef.current) {
thumbnailInputRef.current.value = ''; thumbnailInputRef.current.value = '';
} }
}; };
const handleImagesChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleImagesChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files ?? []); const files = Array.from(e.target.files ?? []);
if (!files.length) { if (!files.length) {
return; return;
} }
const newPreviews = files.map(file => ({ const newPreviews = files.map(file => ({
file, file,
@ -99,8 +98,8 @@ return;
setData('images', [...data.images, ...files]); setData('images', [...data.images, ...files]);
if (imagesInputRef.current) { if (imagesInputRef.current) {
imagesInputRef.current.value = ''; imagesInputRef.current.value = '';
} }
}; };
const removeImage = (index: number) => { const removeImage = (index: number) => {
@ -413,15 +412,13 @@ imagesInputRef.current.value = '';
<FieldError error={errors['images']} label="Galeri Gambar" className="text-xs" /> <FieldError error={errors['images']} label="Galeri Gambar" className="text-xs" />
</CardContent> </CardContent>
</Card> </Card>
<div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm">
<Button type="submit" className="w-full gap-2" disabled={processing}>
{processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</div> </div>
</div> </div>
<Button type="submit" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</form> </form>
</div> </div>

View File

@ -1,29 +1,29 @@
import { Head, useForm, Link } from '@inertiajs/react';
import { ImagePlus, X, Upload, ArrowLeft, Save, Loader } from 'lucide-react';
import React, { useRef, useState } from 'react';
import { NumericFormat } from 'react-number-format';
import { toast } from 'sonner';
import { SimpleEditor } from '@/components/tiptap-templates/simple/simple-editor'; import { SimpleEditor } from '@/components/tiptap-templates/simple/simple-editor';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { import {
Combobox, Combobox,
ComboboxChip,
ComboboxChips,
ComboboxChipsInput,
ComboboxContent, ComboboxContent,
ComboboxEmpty, ComboboxEmpty,
ComboboxItem, ComboboxItem,
ComboboxList, ComboboxList,
ComboboxChips,
ComboboxChip,
ComboboxChipsInput,
ComboboxValue, ComboboxValue,
useComboboxAnchor, useComboboxAnchor,
} from "@/components/ui/combobox" } from "@/components/ui/combobox";
import { Field, FieldError } from "@/components/ui/field" import { Field, FieldError } from "@/components/ui/field";
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label";
import productRoutes from '@/routes/product'; import productRoutes from '@/routes/product';
import type { Product, ProductPrice } from '@/types'; import type { Product, ProductPrice } from '@/types';
import type { Category } from '@/types/category'; import type { Category } from '@/types/category';
import { Head, Link, useForm } from '@inertiajs/react';
import { ArrowLeft, ImagePlus, Loader, Save, Upload, X } from 'lucide-react';
import React, { useRef, useState } from 'react';
import { NumericFormat } from 'react-number-format';
import { toast } from 'sonner';
interface ExistingImage { interface ExistingImage {
kind: 'existing'; kind: 'existing';
@ -101,8 +101,8 @@ export default function ProductEdit({ product, categories }: { product: Product,
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (!file) { if (!file) {
return; return;
} }
setData('thumbnail', file); setData('thumbnail', file);
setThumbnailCleared(false); setThumbnailCleared(false);
@ -117,16 +117,16 @@ return;
setThumbnailCleared(true); setThumbnailCleared(true);
if (thumbnailInputRef.current) { if (thumbnailInputRef.current) {
thumbnailInputRef.current.value = ''; thumbnailInputRef.current.value = '';
} }
}; };
const handleImagesChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleImagesChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files ?? []); const files = Array.from(e.target.files ?? []);
if (!files.length) { if (!files.length) {
return; return;
} }
const newItems: NewImage[] = files.map(file => ({ const newItems: NewImage[] = files.map(file => ({
kind: 'new', kind: 'new',
@ -138,8 +138,8 @@ return;
setData('images', [...data.images, ...files]); setData('images', [...data.images, ...files]);
if (imagesInputRef.current) { if (imagesInputRef.current) {
imagesInputRef.current.value = ''; imagesInputRef.current.value = '';
} }
}; };
const removeGalleryItem = (index: number) => { const removeGalleryItem = (index: number) => {
@ -481,17 +481,13 @@ imagesInputRef.current.value = '';
<FieldError error={errors['images']} label="Galeri Gambar" className="text-xs" /> <FieldError error={errors['images']} label="Galeri Gambar" className="text-xs" />
</CardContent> </CardContent>
</Card> </Card>
<div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm">
<div className="flex flex-col gap-2">
<Button type="submit" className="w-full gap-2" disabled={processing}>
{processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</div>
</div> </div>
</div> </div>
<Button type="submit" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</form> </form>
</div> </div>

View File

@ -1,22 +1,21 @@
import { Head, Link } from '@inertiajs/react';
import { useForm } from '@inertiajs/react';
import { ArrowLeft, Loader, Save } from 'lucide-react';
import React from 'react';
import { NumericFormat } from 'react-number-format';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Calendar } from "@/components/ui/calendar" import { Calendar } from "@/components/ui/calendar";
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Field, FieldError } from "@/components/ui/field" import { Field, FieldError } from "@/components/ui/field";
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label";
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover" } from "@/components/ui/popover";
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import userRoutes from '@/routes/user'; import userRoutes from '@/routes/user';
import { Head, Link, useForm } from '@inertiajs/react';
import { ArrowLeft, Loader, Save } from 'lucide-react';
import React from 'react';
import { NumericFormat } from 'react-number-format';
import { toast } from 'sonner';
export default function UserCreate() { export default function UserCreate() {
const [isCalendarOpen, setIsCalendarOpen] = React.useState(false); const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
@ -223,15 +222,13 @@ export default function UserCreate() {
</Field> </Field>
</CardContent> </CardContent>
</Card> </Card>
<div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm">
<Button type="submit" className="w-full" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</div> </div>
</div> </div>
<Button type="submit" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</form> </form>
</div> </div>

View File

@ -1,22 +1,21 @@
import { Head, Link } from '@inertiajs/react';
import { useForm } from '@inertiajs/react';
import { ArrowLeft, Loader, Save } from 'lucide-react';
import React from 'react';
import { NumericFormat } from 'react-number-format';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Calendar } from "@/components/ui/calendar" import { Calendar } from "@/components/ui/calendar";
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Field, FieldError } from "@/components/ui/field" import { Field, FieldError } from "@/components/ui/field";
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label";
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover" } from "@/components/ui/popover";
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import userRoutes from '@/routes/user'; import userRoutes from '@/routes/user';
import { Head, Link, useForm } from '@inertiajs/react';
import { ArrowLeft, Loader, Save } from 'lucide-react';
import React from 'react';
import { NumericFormat } from 'react-number-format';
import { toast } from 'sonner';
export default function UserEdit({ user }: { user: any }) { export default function UserEdit({ user }: { user: any }) {
const [isCalendarOpen, setIsCalendarOpen] = React.useState(false); const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
@ -224,17 +223,14 @@ export default function UserEdit({ user }: { user: any }) {
</Field> </Field>
</CardContent> </CardContent>
</Card> </Card>
<div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm">
<Button type="submit" className="w-full" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</div> </div>
</div> </div>
</form>
<Button type="submit" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</form>
</div> </div>
); );
} }

View File

@ -199,15 +199,13 @@ export default function Profile({
</Field> </Field>
</CardContent> </CardContent>
</Card> </Card>
<div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm">
<Button type="submit" className="w-full" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</div> </div>
</div> </div>
<Button type="submit" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</form> </form>
</div> </div>
); );

View File

@ -144,16 +144,14 @@ export default function Security({
<FieldError error={errors.password_confirmation} label="Konfirmasi kata sandi baru" className="text-xs" /> <FieldError error={errors.password_confirmation} label="Konfirmasi kata sandi baru" className="text-xs" />
</Field> </Field>
</div> </div>
<div className="flex justify-end">
<Button type="submit" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</CardContent> </CardContent>
</Card> </Card>
<Button type="submit" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
{canManageTwoFactor && ( {canManageTwoFactor && (
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm"> <Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
<CardHeader className="border-b"> <CardHeader className="border-b">