feat: add variant search functionality to transaction creation page
This commit is contained in:
parent
e4b49c7ff8
commit
ad4c6d47df
@ -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) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user