import { Form, Head, Link, usePage } from '@inertiajs/react'; import { ArrowLeft, Copy, ClipboardPaste, Check, Plus, Trash2, } from 'lucide-react'; import { useCallback, useRef, useState } from 'react'; import { toast } from 'sonner'; import { ConfirmDialog } from '@/components/dialogs'; import { FileUploadMultiple } from '@/components/inputs'; 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 { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { Textarea } from '@/components/ui/textarea'; import { useProductDraftSave } from '@/hooks/use-product-draft'; import { loadProductDraft } from '@/lib/product-draft'; import { getTemporaryUrl } from '@/lib/upload'; import { index as productIndex, store } from '@/routes/admin/master/products'; type Category = { id: number; name: string; }; type Props = { categories: Category[]; }; const PRICE_TYPES = [ { key: 'distributor', label: 'Distributor' }, { key: 'agent', label: 'Agen' }, { key: 'sub_agent', label: 'Sub Agen' }, { key: 'wholesale', label: 'Grosir' }, { key: 'retail', label: 'Ecer' }, { key: 'tiktok', label: 'TikTok' }, { key: 'shopee', label: 'Shopee' }, { key: 'capital', label: 'Modal' }, { key: 'reject_capital', label: 'Reject Modal' }, { key: 'reject_selling', label: 'Reject Jual' }, ]; function createEmptyPrices(): Array<{ type: string; price: number }> { return PRICE_TYPES.map((pt) => ({ type: pt.key, price: 0 })); } type VariantState = { name: string; stock: number; reject_stock: number; retail_stock: number; photos: Array<{ key: string; url: string | null }>; uploading: boolean; prices: Array<{ type: string; price: number }>; }; export default function ProductCreate({ categories }: Props) { const { auth } = usePage().props as { auth: { user?: { id?: number } } }; const userId = auth.user?.id; const draft = loadProductDraft('create', userId); const [productName, setProductName] = useState(draft?.productName ?? ''); const [status, setStatus] = useState(draft?.status ?? 'active'); const [description, setDescription] = useState(draft?.description ?? ''); const [categoryIds, setCategoryIds] = useState( draft?.categoryIds ?? [], ); const [useSamePrice, setUseSamePrice] = useState( draft?.useSamePrice ?? true, ); const [sharedPrices, setSharedPrices] = useState< Array<{ type: string; price: number }> >(draft?.sharedPrices ?? createEmptyPrices()); const [variants, setVariants] = useState(() => { if (draft?.variants && draft.variants.length > 0) { return draft.variants.map((v) => { const photos = Array.isArray(v.photos) ? v.photos.map((p) => ({ key: p.key, url: getTemporaryUrl(p.key) })) : typeof v.photo === 'string' && v.photo ? [{ key: v.photo, url: getTemporaryUrl(v.photo) }] : []; return { ...v, photos, uploading: false, }; }); } return [ { name: '', stock: 0, reject_stock: 0, retail_stock: 0, photos: [], uploading: false, prices: createEmptyPrices(), }, ]; }); const variantsRef = useRef(variants); variantsRef.current = variants; const draftData = { productName, status, description, categoryIds, useSamePrice, sharedPrices, variants: variants.map((v) => ({ name: v.name, stock: v.stock, reject_stock: v.reject_stock, retail_stock: v.retail_stock, photos: v.photos.map((p) => ({ key: p.key })), prices: v.prices, })), }; useProductDraftSave('create', draftData, userId); const addVariant = useCallback(() => { setVariants((prev) => [ ...prev, { name: '', stock: 0, reject_stock: 0, retail_stock: 0, photos: [], uploading: false, prices: createEmptyPrices(), }, ]); }, []); const removeVariant = useCallback((index: number) => { setVariants((prev) => prev.filter((_, i) => i !== index)); }, []); const updateVariant = useCallback( (index: number, field: keyof VariantState, value: unknown) => { setVariants((prev) => { const updated = [...prev]; (updated[index] as Record)[field] = value; return updated; }); }, [], ); const updateVariantPrice = useCallback( (variantIndex: number, priceIndex: number, value: number) => { setVariants((prev) => { const updated = [...prev]; updated[variantIndex] = { ...updated[variantIndex], prices: updated[variantIndex].prices.map((p, i) => i === priceIndex ? { ...p, price: value } : p, ), }; return updated; }); }, [], ); const updateSharedPrice = useCallback( (priceIndex: number, value: number) => { setSharedPrices((prev) => { const updated = [...prev]; updated[priceIndex] = { ...updated[priceIndex], price: value }; return updated; }); }, [], ); const [copiedIndex, setCopiedIndex] = useState(null); const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [deleteVariantIndex, setDeleteVariantIndex] = useState( null, ); const confirmRemoveVariant = useCallback((index: number) => { setDeleteVariantIndex(index); setDeleteConfirmOpen(true); }, []); const copyPrices = useCallback((variantIndex: number) => { setVariants((prev) => { const prices = prev[variantIndex].prices; navigator.clipboard.writeText(JSON.stringify(prices)); setCopiedIndex(variantIndex); setTimeout(() => setCopiedIndex(null), 1500); return prev; }); }, []); const pastePrices = useCallback((variantIndex: number) => { navigator.clipboard.readText().then((text) => { try { const prices = JSON.parse(text) as Array<{ type: string; price: number; }>; setVariants((prev) => { const updated = [...prev]; updated[variantIndex] = { ...updated[variantIndex], prices, }; return updated; }); } catch { // invalid clipboard data } }); }, []); const applyToAll = useCallback((variantIndex: number) => { setVariants((prev) => { const sourcePrices = prev[variantIndex].prices; return prev.map((v, i) => i === variantIndex ? v : { ...v, prices: [...sourcePrices] }, ); }); }, []); function getPayload() { return { name: productName, status, description, category_ids: categoryIds, use_same_price: useSamePrice, shared_prices: useSamePrice ? sharedPrices.map((p) => ({ type: p.type, price: Number(p.price), })) : [], variants: variantsRef.current.map((v) => ({ name: v.name, stock: Number(v.stock), reject_stock: Number(v.reject_stock), retail_stock: Number(v.retail_stock), photo_keys: v.photos.map((p) => p.key), prices: useSamePrice ? [] : v.prices.map((p) => ({ type: p.type, price: Number(p.price), })), })), }; } return ( <>

Tambah Produk

({ ...data, ...getPayload(), })} onError={() => { toast.error('Ada data yang belum sesuai, silakan periksa kembali input Anda.'); }} > {({ errors, processing }) => ( <>
Informasi Produk
setProductName( e.target.value, ) } placeholder="Masukkan nama produk" />
{categories.map((category) => ( ))}