'use client' import React, { useState, useMemo } from 'react' import { Card, CardContent, Button, Box, Typography, IconButton, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, CircularProgress } from '@mui/material' import Grid from '@mui/material/Grid2' import CustomAutocomplete from '@/@core/components/mui/Autocomplete' import CustomTextField from '@/@core/components/mui/TextField' import ImageUpload from '@/components/ImageUpload' import { DropdownOption } from '@/types/apps/purchaseOrderTypes' import { useVendorActive } from '@/services/queries/vendor' import { useIngredients } from '@/services/queries/ingredients' import { useUnits } from '@/services/queries/units' import { useFilesMutation } from '@/services/mutations/files' import { usePurchaseOrdersMutation } from '@/services/mutations/purchaseOrder' export interface PurchaseOrderRequest { vendor_id: string // uuid.UUID po_number: string transaction_date: string // ISO date string due_date: string // ISO date string reference?: string status?: 'draft' | 'sent' | 'approved' | 'received' | 'cancelled' message?: string items: PurchaseOrderItemRequest[] attachment_file_ids?: string[] // uuid.UUID[] } export interface PurchaseOrderItemRequest { ingredient_id: string // uuid.UUID description?: string quantity: number unit_id: string // uuid.UUID amount: number } export type IngredientItem = { id: string organization_id: string outlet_id: string name: string unit_id: string cost: number stock: number is_semi_finished: boolean is_active: boolean metadata: Record created_at: string updated_at: string unit: Unit } export type Unit = { id: string name: string // Add other unit properties as needed } // Internal form state interface for UI management interface PurchaseOrderFormData { vendor: { label: string; value: string } | null po_number: string transaction_date: string due_date: string reference: string status: 'draft' | 'sent' | 'approved' | 'received' | 'cancelled' showPesan: boolean showAttachment: boolean message: string items: PurchaseOrderFormItem[] attachment_file_ids: string[] } interface PurchaseOrderFormItem { id: number // for UI tracking ingredient: { label: string; value: string; originalData?: IngredientItem } | null description: string quantity: number unit: { label: string; value: string } | null amount: number total: number // calculated field for UI } const PurchaseAddForm: React.FC = () => { const [imageUrl, setImageUrl] = useState('') const [formData, setFormData] = useState({ vendor: null, po_number: '', transaction_date: '', due_date: '', reference: '', status: 'draft', // Bottom section toggles showPesan: false, showAttachment: false, message: '', // Items items: [ { id: 1, ingredient: null, description: '', quantity: 1, unit: null, amount: 0, total: 0 } ], attachment_file_ids: [] }) // API Hooks const { data: vendors, isLoading: isLoadingVendors } = useVendorActive() const { data: ingredients, isLoading: isLoadingIngredients } = useIngredients() const { data: units, isLoading: isLoadingUnits } = useUnits({ page: 1, limit: 50 }) const { mutate, isPending } = useFilesMutation().uploadFile const { createPurchaseOrder } = usePurchaseOrdersMutation() // Transform vendors data to dropdown options const vendorOptions: DropdownOption[] = useMemo(() => { return ( vendors?.map(vendor => ({ label: vendor.name, value: vendor.id })) || [] ) }, [vendors]) // Transform ingredients data to autocomplete options format const ingredientOptions = useMemo(() => { if (!ingredients || isLoadingIngredients) { return [] } return ingredients?.data.map((ingredient: IngredientItem) => ({ label: ingredient.name, value: ingredient.id, id: ingredient.id, originalData: ingredient // This includes the full IngredientItem with unit, cost, etc. })) }, [ingredients, isLoadingIngredients]) // Transform units data to dropdown options const unitOptions = useMemo(() => { if (!units || isLoadingUnits) { return [] } return ( units?.data?.map((unit: any) => ({ label: unit.name || unit.nama || unit.unit_name, value: unit.id || unit.code || unit.value })) || [] ) }, [units, isLoadingUnits]) // Handler Functions const handleInputChange = (field: keyof PurchaseOrderFormData, value: any): void => { setFormData(prev => ({ ...prev, [field]: value })) } const handleItemChange = (index: number, field: keyof PurchaseOrderFormItem, value: any): void => { setFormData(prev => { const newItems = [...prev.items] newItems[index] = { ...newItems[index], [field]: value } // Auto-calculate total if amount or quantity changes if (field === 'amount' || field === 'quantity') { const item = newItems[index] item.total = item.amount * item.quantity } return { ...prev, items: newItems } }) } const handleIngredientSelection = (index: number, selectedIngredient: any) => { handleItemChange(index, 'ingredient', selectedIngredient) // Auto-populate related fields if available in the ingredient data if (selectedIngredient) { const ingredientData: IngredientItem = selectedIngredient.originalData || selectedIngredient // Auto-fill unit based on IngredientItem structure if (ingredientData.unit_id || ingredientData.unit) { let unitToFind = null // If ingredient has unit object (populated relation) if (ingredientData.unit && typeof ingredientData.unit === 'object') { unitToFind = ingredientData.unit } // If ingredient has unit_id, find the unit from unitOptions else if (ingredientData.unit_id) { unitToFind = unitOptions.find(option => option.value === ingredientData.unit_id) } if (unitToFind) { // Create unit option object const unitOption = { label: (unitToFind as any).label || (unitToFind as any).name || (unitToFind as any).unit_name, value: (unitToFind as any).value || ingredientData.unit_id } handleItemChange(index, 'unit', unitOption) } } // Auto-fill amount with cost from IngredientItem if (ingredientData.cost !== undefined && ingredientData.cost !== null) { handleItemChange(index, 'amount', ingredientData.cost) } // Auto-fill description with ingredient name if (ingredientData.name) { handleItemChange(index, 'description', ingredientData.name) } } } const addItem = (): void => { const newItem: PurchaseOrderFormItem = { id: Date.now(), ingredient: null, description: '', quantity: 1, unit: null, amount: 0, total: 0 } setFormData(prev => ({ ...prev, items: [...prev.items, newItem] })) } const removeItem = (index: number): void => { setFormData(prev => ({ ...prev, items: prev.items.filter((_, i) => i !== index) })) } // Function to get selected vendor data const getSelectedVendorData = () => { if (!formData.vendor?.value || !vendors) return null const selectedVendor = vendors.find(vendor => vendor.id === (formData?.vendor?.value ?? '')) return selectedVendor } const upsertAttachment = (attachments: string[], newId: string, index = 0) => { if (attachments.length === 0) { return [newId] } return attachments.map((id, i) => (i === index ? newId : id)) } const handleUpload = async (file: File): Promise => { return new Promise((resolve, reject) => { const formData = new FormData() formData.append('file', file) formData.append('file_type', 'image') formData.append('description', 'Purchase image') mutate(formData, { onSuccess: data => { // pemakaian: setFormData(prev => ({ ...prev, attachment_file_ids: upsertAttachment(prev.attachment_file_ids, data.id) })) setImageUrl(data.file_url) resolve(data.id) // <-- balikin id file yang berhasil diupload }, onError: error => { reject(error) // biar async/await bisa tangkep error } }) }) } // Calculate subtotal from items const subtotal = formData.items.reduce((sum, item) => sum + (item.total || 0), 0) // Convert form data to API request format const convertToApiRequest = (): PurchaseOrderRequest => { return { vendor_id: formData.vendor?.value || '', po_number: formData.po_number, transaction_date: formData.transaction_date, due_date: formData.due_date, reference: formData.reference || undefined, status: formData.status, message: formData.message || undefined, items: formData.items .filter(item => item.ingredient && item.unit) // Only include valid items .map(item => ({ ingredient_id: item.ingredient!.value, description: item.description || undefined, quantity: item.quantity, unit_id: item.unit!.value, amount: item.amount })), attachment_file_ids: formData.attachment_file_ids.length > 0 ? formData.attachment_file_ids : undefined } } const handleSave = () => { createPurchaseOrder.mutate(convertToApiRequest(), { onSuccess: () => { window.history.back() } }) } return ( {/* BASIC INFO SECTION */} {/* Row 1 - Vendor and PO Number */} { handleInputChange('vendor', newValue) if (newValue?.value) { const selectedVendorData = vendors?.find(vendor => vendor.id === newValue.value) console.log('Vendor selected:', selectedVendorData) } }} loading={isLoadingVendors} renderInput={params => ( )} /> {getSelectedVendorData() && ( {getSelectedVendorData()?.contact_person ?? ''} {getSelectedVendorData()?.address ?? '-'} {getSelectedVendorData()?.phone_number ?? '-'} )} ) => handleInputChange('po_number', e.target.value)} /> {/* Row 2 - Transaction Date, Due Date, Status */} ) => handleInputChange('transaction_date', e.target.value) } InputLabelProps={{ shrink: true }} /> ) => handleInputChange('due_date', e.target.value)} InputLabelProps={{ shrink: true }} /> ) => handleInputChange('reference', e.target.value)} /> {/* ITEMS TABLE SECTION */} Purchase Order Items Ingredient Description Quantity Unit Amount Total {formData.items.map((item: PurchaseOrderFormItem, index: number) => ( handleIngredientSelection(index, newValue)} loading={isLoadingIngredients} getOptionLabel={(option: any) => { if (!option) return '' return option.label || option.name || option.nama || '' }} isOptionEqualToValue={(option: any, value: any) => { if (!option || !value) return false const optionId = option.value || option.id const valueId = value.value || value.id return optionId === valueId }} renderInput={params => ( {isLoadingIngredients ? : null} {params.InputProps.endAdornment} ) }} /> )} disabled={isLoadingIngredients} /> ) => handleItemChange(index, 'description', e.target.value) } placeholder='Description' /> ) => handleItemChange(index, 'quantity', parseInt(e.target.value) || 1) } inputProps={{ min: 1 }} /> handleItemChange(index, 'unit', newValue)} loading={isLoadingUnits} getOptionLabel={(option: any) => { if (!option) return '' return option.label || option.name || option.nama || '' }} renderInput={params => ( {isLoadingUnits ? : null} {params.InputProps.endAdornment} ) }} /> )} disabled={isLoadingUnits} /> ) => { const value = e.target.value if (value === '') { handleItemChange(index, 'amount', 0) return } const numericValue = parseFloat(value) handleItemChange(index, 'amount', isNaN(numericValue) ? 0 : numericValue) }} inputProps={{ min: 0, step: 'any' }} placeholder='0' /> removeItem(index)} disabled={formData.items.length === 1} > ))}
{/* Add New Item Button */}
{/* SUMMARY SECTION */} {/* Left Side - Message and Attachment */} {/* Message Section */} {formData.showPesan && ( ) => handleInputChange('message', e.target.value) } /> )} {/* Attachment Section */} {formData.showAttachment && ( )} {/* Right Side - Totals */} {/* Sub Total */} Sub Total {new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(subtotal)} {/* Total */} Total {new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(subtotal)} {/* Save Button */}
) } export default PurchaseAddForm