'use no memo'; import { Form, Head, Link } from '@inertiajs/react'; import { ArrowLeft, Minus, Plus, ShoppingCart, Trash2, } from 'lucide-react'; import { useCallback, useMemo, useState } from 'react'; import { toast } from 'sonner'; import { ConfirmDialog } from '@/components/dialogs'; import { FileUpload } from '@/components/inputs'; import { ImagePreviewModal } from '@/components/dialogs'; import { InputError } from '@/components/ui'; import { NumberInput } from '@/components/inputs'; import { RupiahInput } from '@/components/inputs'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from '@/components/ui/combobox'; import { Label } from '@/components/ui/label'; import { Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle, } from '@/components/ui/sheet'; import { Textarea } from '@/components/ui/textarea'; import { formatNumber } from '@/lib/format'; import { formatCurrency } from '@/lib/utils'; import { index as purchaseIndex, update, } from '@/routes/admin/manage/purchases'; import type { PurchaseCreateData, PurchaseForEdit } from './columns'; type CartLine = { key: string; photoUrl: string | null; title: string; subtitle: string; price: number; quantity: number; onAdjust: (delta: number) => void; onSet: (value: number) => void; onRemove: () => void; }; type Props = { purchase: PurchaseForEdit; data: PurchaseCreateData; }; export default function PurchaseEdit({ purchase, data }: Props) { const { suppliers, rawMaterials } = data; const [supplierId, setSupplierId] = useState(String(purchase.supplier_id)); const selectedSupplier = suppliers.find((s) => String(s.id) === supplierId) ?? null; const [discount, setDiscount] = useState(purchase.discount); const [shippingCost, setShippingCost] = useState(purchase.shipping_cost); const [notes, setNotes] = useState(purchase.notes ?? ''); const [photo, setPhoto] = useState(purchase.photo_key); const [photoUrl, setPhotoUrl] = useState(purchase.photo_url); const [uploading, setUploading] = useState(false); const [selectedMaterialName, setSelectedMaterialName] = useState( purchase.existing_material_name ?? '', ); const [quantities, setQuantities] = useState>(() => Object.fromEntries( Object.entries(purchase.existing_quantities).map(([id, qty]) => [ Number(id), qty, ]), ), ); const [cartOpen, setCartOpen] = useState(false); const [previewKey, setPreviewKey] = useState(null); const [cartRemoveKey, setCartRemoveKey] = useState(null); const priceMap = useMemo( () => new Map( rawMaterials.flatMap((m) => m.raw_material_prices.map((p) => [p.id, p]), ), ), [rawMaterials], ); const materialByPriceId = useMemo( () => new Map( rawMaterials.flatMap((m) => m.raw_material_prices.map((p) => [ p.id, { name: m.name, unit: m.unit }, ]), ), ), [rawMaterials], ); const subtotal = Object.entries(quantities).reduce( (sum, [priceId, quantity]) => { const price = priceMap.get(Number(priceId)); return sum + (price ? price.price * quantity : 0); }, 0, ); const total = subtotal - discount + shippingCost; const selectedMaterial = useMemo( () => rawMaterials.find((m) => m.name === selectedMaterialName) ?? null, [rawMaterials, selectedMaterialName], ); const updateQuantity = useCallback((priceId: number, value: number) => { setQuantities((prev) => ({ ...prev, [priceId]: Math.max(0, value), })); }, []); const incrementQuantity = useCallback((priceId: number, amount: number) => { setQuantities((prev) => ({ ...prev, [priceId]: Math.max(0, (prev[priceId] ?? 0) + amount), })); }, []); const cartItems: CartLine[] = (() => { const lines: CartLine[] = []; for (const [priceId, quantity] of Object.entries(quantities)) { if (quantity <= 0) { continue; } const id = Number(priceId); const price = priceMap.get(id); const material = materialByPriceId.get(id); if (price && material) { lines.push({ key: `existing-${id}`, photoUrl: price.photo_url, title: `${material.name} — ${price.variant}`, subtitle: `${formatCurrency(price.price)} / ${material.unit}`, price: price.price, quantity, onAdjust: (delta) => incrementQuantity(id, delta), onSet: (value) => updateQuantity(id, value), onRemove: () => updateQuantity(id, 0), }); } } return lines.sort((a, b) => a.title.localeCompare(b.title)); })(); function formatQuantity(value: number): string { return formatNumber(value, { maximumFractionDigits: 4 }); } function getPayload() { return { mode: 'existing', supplier_id: supplierId ? Number(supplierId) : null, discount, shipping_cost: shippingCost, notes: notes || null, photo_key: photo, existing_items: Object.entries(quantities) .map(([priceId, quantity]) => ({ raw_material_price_id: Number(priceId), quantity: Number(quantity), unit_price: priceMap.get(Number(priceId))?.price ?? 0, })) .filter((item) => item.quantity > 0), }; } return ( <>

Edit Belanja

({ ...data, ...getPayload(), })} onError={() => { toast.error('Terjadi kesalahan saat menyimpan data. Silakan periksa kembali input Anda.'); }} > {({ errors, processing }) => (
Pilih Bahan Baku
m.name} value={selectedMaterial} onValueChange={( value, ) => setSelectedMaterialName( value?.name ?? '', ) } > Tidak ada bahan baku ditemukan. {(m) => ( {m.name}{' '} ( {m.unit} ) )}
{selectedMaterial && (
{selectedMaterial.raw_material_prices.map( (price) => (
0 ? 'flex items-center justify-between gap-3 rounded-lg border border-primary p-3' : 'flex items-center justify-between gap-3 rounded-lg border p-3' } >
{price.photo_url ? ( { ) : (
N/A
)}

{ price.variant }

Stok:{' '} {formatQuantity( Number( price.stock, ), )}{' '} { selectedMaterial.unit }{' '} ·{' '} {formatCurrency( price.price, )}

updateQuantity( price.id, val, ) } />
), )}
)}
Ringkasan
s.name } value={selectedSupplier} onValueChange={(value) => setSupplierId( value ? String(value.id) : '', ) } > Tidak ada supplier ditemukan. {(s) => ( {s.name} )}
Subtotal {formatCurrency(subtotal)}
Diskon
Ongkir
Total {formatCurrency(total)}