import React, { useState, useEffect, useMemo, useRef } from 'react'; import { Head } from '@inertiajs/react'; import AppLayout from '@/Layouts/AppLayout'; import { MaterialReactTable, type MRT_ColumnDef, type MRT_PaginationState, type MRT_SortingState, } from 'material-react-table'; import axios from 'axios'; import { toast } from 'react-hot-toast'; import { Combobox } from '@headlessui/react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faChevronDown, faCheck } from '@fortawesome/free-solid-svg-icons'; // --- Interfaces --- interface DropdownOption { id: number; name: string; } interface StockEntry { id: number; physical_item_id: number | null; supplier_id: number | null; count: number; price: number | null; bought: string | null; description: string | null; note: string | null; stock_position_id: number | null; country_of_origin_id: number | null; on_the_way: boolean; stock_batch_id: number | null; created_by?: number; updated_by?: number; physical_item?: DropdownOption; supplier?: DropdownOption; stock_position?: { id: number; line: string; rack: string; shelf: string; position: string }; } interface StockEntryFormData { physical_item_id: number | null; supplier_id: number | null; count: number; price: number | null; bought: string | null; description: string | null; note: string | null; stock_position_id: number | null; country_of_origin_id: number | null; on_the_way: boolean; stock_batch_id: number | null; } const defaultForm = { physical_item_id: null, supplier_id: null, count: 0, price: null, bought: null, description: null, note: null, stock_position_id: null, country_of_origin_id: null, on_the_way: false, stock_batch_id: null, }; export default function StockEntries() { // Table state const [data, setData] = useState([]); const [isError, setIsError] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isRefetching, setIsRefetching] = useState(false); const [rowCount, setRowCount] = useState(0); // Dropdowns const [stockPositions, setStockPositions] = useState([]); const [suppliers, setSuppliers] = useState([]); const [physicalItems, setPhysicalItems] = useState([]); const [originCountries, setOriginCountries] = useState([]); // Modal/form state const [editingEntry, setEditingEntry] = useState(null); const [formData, setFormData] = useState(defaultForm); // Batch mode state const [isBatchMode, setIsBatchMode] = useState(false); const [batchSelections, setBatchSelections] = useState([]); // Combobox search const [itemQuery, setItemQuery] = useState(''); const filteredItems = useMemo( () => itemQuery === '' ? physicalItems : physicalItems.filter(item => item.name.toLowerCase().includes(itemQuery.toLowerCase()), ), [itemQuery, physicalItems], ); // Pagination/sorting/filtering const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 }); const [sorting, setSorting] = useState([{ id: 'updated_at', desc: true }]); const [globalFilter, setGlobalFilter] = useState(''); // Modal ref const dialogRef = useRef(null); const openModal = () => dialogRef.current?.showModal(); const closeModal = () => dialogRef.current?.close(); // Columns const columns = useMemo[]>( () => [ { accessorKey: 'id', header: 'ID', size: 80 }, { accessorKey: 'physical_item.name', header: 'Physical Item', size: 200 }, { accessorKey: 'supplier.name', header: 'Supplier', size: 150 }, { accessorKey: 'physical_item.manufacturer.name', header: 'Brand', size: 150 }, { accessorKey: 'count', header: 'Count', size: 100 }, { accessorKey: 'price', header: 'Price', size: 100, Cell: ({ cell }) => cell.getValue()?.toFixed(2) || '-' }, { accessorKey: 'bought', header: 'Bought Date', size: 120 }, { accessorKey: 'on_the_way', header: 'On The Way', size: 100, Cell: ({ cell }) => cell.getValue() ? 'Yes' : 'No' }, { accessorKey: 'stock_position', header: 'Position', size: 150, Cell: ({ row }) => { const pos = row.original.stock_position; return pos ? `${pos.line}-${pos.rack}-${pos.shelf}-${pos.position}` : '-'; } }, { accessorKey: 'updated_at', header: 'Last Updated', size: 150 }, ], [], ); // Batch columns const batchColumns = useMemo[]>( () => [ { accessorKey: 'select', header: 'Select', Cell: ({ row }) => { const id = row.original.id; return ( setBatchSelections(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id] )} /> ); }, }, { accessorKey: 'id', header: 'ID' }, { accessorKey: 'physical_item.name', header: 'Item' }, { accessorKey: 'supplier.name', header: 'Supplier' }, ], [batchSelections], ); // Fetch options & data useEffect(() => { fetchData(); }, [pagination.pageIndex, pagination.pageSize, sorting, globalFilter]); useEffect(() => { fetchOptions(); }, []); async function fetchOptions() { try { const { data } = await axios.get('/api/stockData/options'); setStockPositions(data.stockPositions); setSuppliers(data.suppliers); setOriginCountries(data.countriesOrigin); } catch { toast.error('Failed to load form options'); } } async function fetchData() { setIsLoading(!data.length); setIsRefetching(!!data.length); try { const res = await axios.get('/api/stockData'); setData(res.data.data); setRowCount(res.data.meta.total); } catch { setIsError(true); toast.error('Failed to fetch stock entries'); } finally { setIsLoading(false); setIsRefetching(false); } } async function fetchPhysicalItems(query: string) { try { const res = await axios.get('/api/stockData/options/items', { params: { item_name: query } }); setPhysicalItems(res.data.physicalItems); } catch { toast.error('Failed to load items'); } } useEffect(() => { const delay = setTimeout(() => itemQuery && fetchPhysicalItems(itemQuery), 500); return () => clearTimeout(delay); }, [itemQuery]); // Input change const handleInputChange = (e: React.ChangeEvent) => { const { name, value, type } = e.target; setFormData(prev => ({ ...prev, [name]: type === 'checkbox' ? (e.target as HTMLInputElement).checked : type === 'number' ? parseFloat(value) || null : value || null, })); }; // Submit const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { let res; if (editingEntry) { res = await axios.put(`/api/stockData/${editingEntry.id}`, formData); toast.success('Stock entry updated'); } else { res = await axios.post('/api/stockData', formData); toast.success('Stock entry created'); } const newEntry: StockEntry = res.data; if (isBatchMode) { // Add to batch and keep modal open setBatchSelections(prev => [...prev, newEntry.id]); setFormData({ ...defaultForm, on_the_way: true }); } else { closeModal(); } fetchData(); } catch { toast.error('Failed to save'); } }; // Batch submit const handleBatchSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { await axios.post('/api/stockData/batch', { ids: batchSelections }); toast.success('Batch created'); closeModal(); fetchData(); } catch { toast.error('Batch submit failed'); } }; const handleEdit = (entry: StockEntry) => { setEditingEntry(entry); setFormData({ ...entry }); openModal(); }; const handleDelete = async (id: number) => { if (!confirm('Are you sure?')) return; try { await axios.delete(`/api/stockData/${id}`); toast.success('Deleted'); fetchData(); } catch { toast.error('Failed to delete'); } }; const handleAdd = () => { setIsBatchMode(false); setEditingEntry(null); setFormData(defaultForm); openModal(); }; const handleNewBatch = () => { setIsBatchMode(true); setEditingEntry(null); setBatchSelections([]); setFormData({ ...defaultForm, on_the_way: true }); openModal(); }; return ( (

Stock Entries

)}>

Stock Entries

[ , , ]} />
{/* Modal (Add/Edit & Batch) */}
{isBatchMode && (

Select Incoming Items

e.on_the_way)} enablePagination={false} enableSorting={false} enableGlobalFilter={false} getRowId={row => row.id.toString()} />
)}

{isBatchMode ? 'New Batch Entry' : (editingEntry ? 'Edit Stock Entry' : 'New Stock Entry')}

{/* Physical Item */}
setFormData(prev => ({ ...prev, physical_item_id: val }))}> setItemQuery(e.target.value)} displayValue={id => physicalItems.find(i => i.id === id)?.name || ''} placeholder="Select item..." className="input" /> {filteredItems.map(item => (
{item.name} {formData.physical_item_id === item.id && }
))}
{/* Supplier */}
{/* Count, Price, Bought Date */}
{/* Description & Note */}