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, FieldError } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import * as orderRoutes from '@/routes/order'; import React, { useMemo, useState } from 'react'; import { toast } from 'sonner'; import { Product, ProductPrice } from '@/types'; import { Order, OrderItem } from '@/types/order'; import { Plus, Trash2, CalendarIcon, Minus, ShoppingCart, Search, ImagePlus, X, Tag, ArrowLeft, Check, Save, Loader } from 'lucide-react'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { format } from 'date-fns'; import { cn } from '@/lib/utils'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from '@/components/ui/empty'; import { Sheet, SheetContent, SheetTrigger, SheetClose, } from "@/components/ui/sheet"; import { NumericFormat } from 'react-number-format'; type EnumOption = { value: string, label: string }; export default function OrderEdit({ order, products, orderStatus, orderChannels, paymentMethods, priceTypes }: { order: Order, products: Product[], orderStatus: EnumOption[], orderChannels: EnumOption[], paymentMethods: EnumOption[], priceTypes: EnumOption[] }) { const [search, setSearch] = useState(''); const [selectedCategory, setSelectedCategory] = useState('all'); const [qtyDialogIndex, setQtyDialogIndex] = useState<{ product_id: number, price_type: string } | null>(null); const [qtyInputValue, setQtyInputValue] = useState(''); const [globalPriceType, setGlobalPriceType] = useState('retail'); const categories = useMemo(() => { const map = new Map(); products.forEach(p => p.categories?.forEach(c => map.set(c.id, c.name))); return Array.from(map.entries()).map(([id, name]) => ({ id, name })); }, [products]); const { data, setData, patch, processing, errors } = useForm({ invoice_number: order.invoice_number, customer_name: order.customer_name, discount: order.discount, payment: order.payment, payment_method: order.payment_method, order_status: order.order_status, order_channel: order.order_channel, items: (order.items?.map(item => ({ product_id: item.product_id, qty: item.qty, price: item.price, total: item.total, price_type: item.price_type, product: products.find(p => p.id === item.product_id) || item.product })) ?? []) as any[], }); const filteredProducts = products.filter(p => { const matchesSearch = p.name.toLowerCase().includes(search.toLowerCase()); const matchesCategory = selectedCategory === 'all' || p.categories?.some(c => String(c.id) === selectedCategory); return matchesSearch && matchesCategory; }); const findItemIndex = (productId: number, priceType: string) => data.items.findIndex(item => item.product_id === productId && item.price_type === priceType); const getProductPrice = (product: Product, priceType: string) => { return product.prices?.find(p => p.price_type === priceType)?.price || 0; }; const addToCart = (product: Product, priceType: string) => { const index = findItemIndex(product.id, priceType); const price = getProductPrice(product, priceType); if (index > -1) { const newItems = [...data.items]; newItems[index].qty += 1; newItems[index].total = newItems[index].qty * newItems[index].price; setData('items', newItems); } else { setData('items', [ ...data.items, { product_id: product.id, qty: 1, price: price, total: price, price_type: priceType, product: product } ]); } }; const decreaseQuantity = (productId: number, priceType: string, e: React.MouseEvent) => { e.stopPropagation(); const index = findItemIndex(productId, priceType); if (index === -1) return; const newItems = [...data.items]; if (newItems[index].qty <= 1) { newItems.splice(index, 1); } else { newItems[index].qty -= 1; newItems[index].total = newItems[index].qty * newItems[index].price; } setData('items', newItems); }; const increaseQuantity = (productId: number, priceType: string, e: React.MouseEvent) => { e.stopPropagation(); const product = products.find(p => p.id === productId); if (product) addToCart(product, priceType); }; const removeFromCart = (productId: number, priceType: string) => { const newItems = data.items.filter(item => !(item.product_id === productId && item.price_type === priceType)); setData('items', newItems); }; const updateCartQuantity = (productId: number, priceType: string, qty: number) => { if (qty < 1) return; const index = findItemIndex(productId, priceType); if (index > -1) { const newItems = [...data.items]; newItems[index].qty = qty; newItems[index].total = qty * newItems[index].price; setData('items', newItems); } }; const openQtyDialog = (item: any, e: React.MouseEvent) => { e.stopPropagation(); setQtyInputValue(String(item.qty)); setQtyDialogIndex({ product_id: item.product_id, price_type: item.price_type }); }; const confirmQty = () => { const val = parseInt(qtyInputValue); if (!isNaN(val) && val >= 1 && qtyDialogIndex) { updateCartQuantity(qtyDialogIndex.product_id, qtyDialogIndex.price_type, val); } setQtyDialogIndex(null); }; const onSubmit = (e: React.FormEvent) => { e.preventDefault(); if (data.items.length === 0) { toast.error('Pilih minimal satu produk'); return; } patch(orderRoutes.update(order.id).url, { onSuccess: (response: any) => { toast.success(response.props.flash.success); }, }); }; const subtotal = useMemo(() => { return data.items.reduce((acc, item) => acc + item.total, 0); }, [data.items]); const total = subtotal - data.discount; const change = data.payment - total; const cartTotalItems = data.items.reduce((a, i) => a + i.qty, 0); const PriceTypeLabel = ({ type }: { type: string }) => { const option = priceTypes.find(opt => opt.value === type); return option ? option.label : type; }; const CartFormContent = () => (
Keranjang {cartTotalItems} pcs {data.items.length === 0 ? (

Keranjang masih kosong

) : (
{data.items.map((item, idx) => (
{item.product?.thumbnail_url ? ( {item.product.name} ) : (
)}

{item.product?.name}

Rp {item.total.toLocaleString('id-ID')}

{item.qty}
× Rp {item.price.toLocaleString('id-ID')}
))}
)}
setData('customer_name', e.target.value)} />
Subtotal Rp {subtotal.toLocaleString('id-ID')}
Potongan / Diskon
{ setData('discount', values.floatValue || 0) }} placeholder="Rp 0" autoComplete='off' />
Total Rp {total.toLocaleString('id-ID')}
{ setData('payment', values.floatValue || 0) }} placeholder="Rp 0" autoComplete='off' /> {change >= 0 && data.payment > 0 && (
Kembalian Rp {change.toLocaleString('id-ID')}
)}
); return (

Ubah Pesanan

setSearch(e.target.value)} />
{filteredProducts.map((product) => { const index = findItemIndex(product.id, globalPriceType); const cartItem = index > -1 ? data.items[index] : null; const currentPrice = getProductPrice(product, globalPriceType); return ( addToCart(product, globalPriceType)} >
{product.thumbnail_url ? ( {product.name} ) : (
)} {cartItem && (
{cartItem.qty}
)}
{product.categories?.map(cat => ( {cat.name} ))}

{product.name}

{/* Price + Stepper */}
e.stopPropagation()} >
Harga Rp {currentPrice.toLocaleString('id-ID')}
); })}
{CartFormContent()}
{CartFormContent()}
{/* Quantity Input Dialog */} { if (!open) setQtyDialogIndex(null); }}> Ubah Jumlah
setQtyInputValue(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') confirmQty(); }} className="text-lg font-bold" autoFocus />
); } OrderEdit.layout = { breadcrumbs: [ { title: 'Kelola' }, ], };