feat: add variant search functionality to transaction creation page

This commit is contained in:
Yoga Pangestu 2026-08-18 14:38:21 +07:00
parent e4b49c7ff8
commit ad4c6d47df

View File

@ -1,7 +1,7 @@
'use no memo';
import { Form, Head, Link, usePage } from '@inertiajs/react';
import { ArrowLeft, Minus, Plus, ShoppingCart, Trash2 } from 'lucide-react';
import { ArrowLeft, Minus, Plus, Search, ShoppingCart, Trash2 } from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { toast } from 'sonner';
import { ConfirmDialog } from '@/components/dialogs';
@ -9,6 +9,7 @@ import { ImagePreviewModal } from '@/components/dialogs';
import { FileUpload } from '@/components/inputs';
import { NumberInput } from '@/components/inputs';
import { RupiahInput } from '@/components/inputs';
import { Input } from '@/components/ui/input';
import { InputError } from '@/components/ui';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@ -117,6 +118,7 @@ export default function TransactionCreate({
const [cartRemoveKey, setCartRemoveKey] = useState<string | null>(null);
const [tiktokOrderId, setTiktokOrderId] = useState(draft?.tiktokOrderId ?? '');
const [shopeeOrderId, setShopeeOrderId] = useState(draft?.shopeeOrderId ?? '');
const [variantSearch, setVariantSearch] = useState('');
const quantitiesRef = useRef(quantities);
@ -162,6 +164,19 @@ export default function TransactionCreate({
[products, selectedProductId],
);
const groupedVariants = useMemo(() => {
if (!variantSearch) return [];
const search = variantSearch.toLowerCase();
return products
.map((p) => ({
...p,
product_variants: p.product_variants.filter((v) =>
v.name.toLowerCase().includes(search),
),
}))
.filter((p) => p.product_variants.length > 0);
}, [products, variantSearch]);
const variantById = useMemo(
() =>
new Map(
@ -414,7 +429,84 @@ export default function TransactionCreate({
/>
</div>
{selectedProduct && (
<div className="grid gap-2">
<Label className="text-sm font-medium">Cari Varian</Label>
<div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Ketik nama varian..."
value={variantSearch}
onChange={(e) => setVariantSearch(e.target.value)}
className="pl-8"
/>
</div>
</div>
{variantSearch && groupedVariants.length > 0 && (
<div className="space-y-4">
{groupedVariants.map((p) => (
<div key={p.id} className="space-y-2">
<p className="text-xs font-semibold text-muted-foreground uppercase">{p.name}</p>
<div className="space-y-2">
{p.product_variants.map((variant) => {
const currentStock =
stockType === 'reject'
? variant.reject_stock
: (isCashier ? variant.retail_stock : variant.stock);
return (
<div
key={variant.id}
className={
(quantities[variant.id] ?? 0) > 0
? 'flex flex-wrap items-center justify-between gap-2 rounded-lg border border-primary p-3'
: 'flex flex-wrap items-center justify-between gap-2 rounded-lg border p-3'
}
>
<div className="flex min-w-0 items-center gap-3">
{variant.photo_url ? (
<img src={variant.photo_url} alt={variant.name} className="h-10 w-10 shrink-0 rounded-md object-cover" />
) : (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-muted text-xs text-muted-foreground">N/A</div>
)}
<div className="min-w-0">
<p className="truncate font-medium">{variant.name}</p>
<p className="text-xs text-muted-foreground">
Stok: {formatQuantity(Number(currentStock))} pcs · {getUnitPrice(variant.id) <= 0 ? (
<span className="text-destructive">Harga belum diatur</span>
) : (
formatCurrency(
stockType === 'reject'
? (variant.prices?.reject_selling ?? 0)
: (isCashier ? (variant.prices?.retail ?? 0) : (variant.prices?.[priceType] ?? 0)),
)
)}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<Button type="button" variant="outline" size="icon" disabled={!(quantities[variant.id] ?? 0)} onClick={() => incrementQuantity(variant.id, -1)}>
<Minus className="h-4 w-4" />
</Button>
<NumberInput className="w-24 text-center" value={quantities[variant.id] ?? 0} onValueChange={(val) => updateQuantity(variant.id, val)} />
<Button type="button" variant="outline" size="icon" disabled={getUnitPrice(variant.id) <= 0} onClick={() => incrementQuantity(variant.id, 1)}>
<Plus className="h-4 w-4" />
</Button>
</div>
</div>
);
})}
</div>
</div>
))}
</div>
)}
{variantSearch && groupedVariants.length === 0 && (
<p className="text-sm text-muted-foreground">Tidak ada varian ditemukan.</p>
)}
{!variantSearch && selectedProduct && (
<div className="space-y-2">
{selectedProduct.product_variants.map(
(variant) => {