'use no memo'; import { Form, Head, Link, usePage } from '@inertiajs/react'; import { ArrowLeft, Check, Layers, Plus, ShoppingCart, Trash2 } from 'lucide-react'; import { useCallback, useMemo, useRef, 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 { 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 { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; 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 { getTemporaryUrl } from '@/lib/upload'; import { formatCurrency } from '@/lib/utils'; import { index as cuttingIndex, update } from '@/routes/admin/manage/cuttings'; import type { CuttingCreateData, CuttingForEdit } from './columns'; type MaterialState = { id?: number; raw_material_price_id: number; material_usage: number; material_result: number; combination_id: number | null; variant: string; material_name: string; unit: string; photo_url: string | null; }; type CombinationState = { id?: number; material_result: number; }; type Props = { cutting: CuttingForEdit; rawMaterials: CuttingCreateData['rawMaterials']; }; export default function CuttingEdit({ cutting, rawMaterials }: Props) { const { errors } = usePage().props as { errors: Record }; const [materials, setMaterials] = useState(() => { const comboIdToIndex = new Map(); cutting.combinations.forEach((c, i) => comboIdToIndex.set(c.id, i)); return cutting.materials.map((m) => { const rawMaterial = rawMaterials.find((rm) => rm.raw_material_prices.some((p) => p.id === m.raw_material_price_id), ); const price = rawMaterial?.raw_material_prices.find( (p) => p.id === m.raw_material_price_id, ); return { id: m.id, raw_material_price_id: m.raw_material_price_id, material_usage: m.material_usage, material_result: m.material_result ?? 0, combination_id: m.combination_id !== null ? (comboIdToIndex.get(m.combination_id) ?? null) : null, variant: price?.variant ?? m.variant ?? '', material_name: rawMaterial?.name ?? '', unit: rawMaterial?.unit ?? '', photo_url: price?.photo_url ?? m.photo_url ?? null, }; }); }); const [combinations, setCombinations] = useState( () => cutting.combinations.map((c) => ({ id: c.id, material_result: c.material_result ?? 0, })), ); const [selectedMaterialName, setSelectedMaterialName] = useState(''); const [productName, setProductName] = useState(cutting.product_name); const [sample, setSample] = useState(cutting.sample); const [originalOutsideSample, setOriginalOutsideSample] = useState(cutting.original_outside_sample); const cuttingResult = sample + originalOutsideSample; const [notes, setNotes] = useState(cutting.description ?? ''); const [photo, setPhoto] = useState(cutting.photo_key); const [photoUrl, setPhotoUrl] = useState(cutting.photo_url); const [uploading, setUploading] = useState(false); const submittingRef = useRef(false); const [cartOpen, setCartOpen] = useState(false); const [previewKey, setPreviewKey] = useState(null); const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [deleteMaterialIndex, setDeleteMaterialIndex] = useState(null); const [cartDeleteConfirmOpen, setCartDeleteConfirmOpen] = useState(false); const [cartDeleteIndex, setCartDeleteIndex] = useState(null); const [comboDeleteConfirmOpen, setComboDeleteConfirmOpen] = useState(false); const [comboDeleteIndex, setComboDeleteIndex] = useState(null); const [comboDialogOpen, setComboDialogOpen] = useState(false); const [comboMaterialName, setComboMaterialName] = useState(''); const [comboSelectedPriceIds, setComboSelectedPriceIds] = useState([]); const [comboResult, setComboResult] = useState(0); const comboMaterial = useMemo( () => rawMaterials.find((m) => m.name === comboMaterialName) ?? null, [rawMaterials, comboMaterialName], ); const materialsRef = useRef(materials); materialsRef.current = materials; const priceMap = useMemo( () => new Map(rawMaterials.flatMap((m) => m.raw_material_prices.map((p) => [p.id, p]))), [rawMaterials], ); const selectedMaterial = useMemo( () => rawMaterials.find((m) => m.name === selectedMaterialName) ?? null, [rawMaterials, selectedMaterialName], ); const addVariant = useCallback( (priceId: number) => { if (!selectedMaterial) { return; } const price = selectedMaterial.raw_material_prices.find((p) => p.id === priceId); if (!price) { return; } setMaterials((prev) => { if (prev.some((m) => m.raw_material_price_id === priceId && m.combination_id === null)) { return prev; } return [ ...prev, { raw_material_price_id: price.id, material_usage: 0, material_result: 0, combination_id: null, variant: price.variant, material_name: selectedMaterial.name, unit: selectedMaterial.unit, photo_url: price.photo_url, }, ]; }); }, [selectedMaterial], ); const openComboDialog = useCallback((materialName: string, preSelectPriceId?: number) => { setComboMaterialName(materialName); setComboSelectedPriceIds(preSelectPriceId ? [preSelectPriceId] : []); setComboResult(0); setComboDialogOpen(true); }, []); const toggleComboPrice = useCallback((priceId: number) => { setComboSelectedPriceIds((prev) => prev.includes(priceId) ? prev.filter((id) => id !== priceId) : [...prev, priceId], ); }, []); const confirmCombo = useCallback(() => { if (comboSelectedPriceIds.length < 2) { return; } const comboIndex = combinations.length; const newMaterials: MaterialState[] = comboSelectedPriceIds.map((priceId) => { let foundMaterial: (typeof rawMaterials)[number] | undefined; let foundPrice: (typeof rawMaterials)[number]['raw_material_prices'][number] | undefined; for (const rm of rawMaterials) { const p = rm.raw_material_prices.find((pp) => pp.id === priceId); if (p) { foundMaterial = rm; foundPrice = p; break; } } return { raw_material_price_id: priceId, material_usage: 0, material_result: 0, combination_id: comboIndex, variant: foundPrice?.variant ?? '', material_name: foundMaterial?.name ?? '', unit: foundMaterial?.unit ?? '', photo_url: foundPrice?.photo_url ?? null, }; }); setMaterials((prev) => [...prev, ...newMaterials]); setCombinations((prev) => [ ...prev, { material_result: comboResult, }, ]); setComboDialogOpen(false); setComboMaterialName(''); setComboSelectedPriceIds([]); setComboResult(0); }, [comboSelectedPriceIds, comboResult, rawMaterials, combinations.length]); const updateMaterial = useCallback( (index: number, field: keyof MaterialState, value: unknown) => { setMaterials((prev) => { const updated = [...prev]; (updated[index] as Record)[field] = value; return updated; }); }, [], ); const updateCombinationResult = useCallback((comboIndex: number, value: number) => { setCombinations((prev) => prev.map((c, i) => (i === comboIndex ? { ...c, material_result: value } : c))); }, []); const totalMaterialCost = useMemo(() => { return materials.reduce((sum, m) => { const price = priceMap.get(m.raw_material_price_id); return sum + (price ? price.price * m.material_usage : 0); }, 0); }, [materials, priceMap]); const totalCost = totalMaterialCost; const costPerUnit = cuttingResult > 0 ? Math.floor(totalCost / cuttingResult) : 0; function formatQuantity(value: number): string { return formatNumber(value, { maximumFractionDigits: 4 }); } function getPayload() { return { description: notes || null, product_name: productName || null, sample: sample, original_outside_sample: originalOutsideSample, cutting_result: cuttingResult, materials: materialsRef.current.map((m) => ({ raw_material_price_id: m.raw_material_price_id, material_usage: m.material_usage, material_result: m.material_result, combination_index: m.combination_id, })), combinations: combinations.map((c) => ({ material_result: c.material_result, })), photo_key: photo, }; } return ( <>

Edit Cutting

({ ...data, ...getPayload() })} onSubmit={() => { submittingRef.current = true; }} onError={() => { toast.error('Ada data yang belum sesuai, 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.length > 0 && (
{selectedMaterial.raw_material_prices.map((price) => { const isAdded = materials.some((m) => m.raw_material_price_id === price.id); const addedCount = materials.filter((m) => m.raw_material_price_id === price.id).length; return (
{price.photo_url ? ( {price.variant} ) : (
N/A
)}

{price.variant}

Stok: {formatNumber(Number(price.stock))} {selectedMaterial.unit} · {formatCurrency(price.price)} {addedCount > 0 && ` · ×${addedCount}`}

); })}
)}
Ringkasan
setProductName(e.target.value)} placeholder="Masukkan nama produk" />
Total {formatCurrency(totalCost)}
{cuttingResult > 0 && (
Biaya Per Produk {formatCurrency(costPerUnit)}
)}