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, Toaster} from 'react-hot-toast'; import type { StockBatch, Supplier } from '@/types'; interface FileWithType { file: File; fileType: 'invoice' | 'label' | 'other'; } export default function StockBatches() { const [data, setData] = useState([]); const [suppliers, setSuppliers] = useState([]); const [isLoading, setIsLoading] = useState(false); const [rowCount, setRowCount] = useState(0); const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10, }); const [sorting, setSorting] = useState([ { id: 'updatedAt', desc: true }, ]); const [globalFilter, setGlobalFilter] = useState(''); const dialogRef = useRef(null); const defaultFormData = { supplierId: null as number | null, tracking_number: '' as string, arrival_date: '' as string, }; const [formData, setFormData] = useState(defaultFormData); const [files, setFiles] = useState([]); // inside your component, above the useMemo… const formatDate = (value: string, withTime = true) => { const d = new Date(value); const dd = String(d.getDate()).padStart(2, '0'); const mm = String(d.getMonth() + 1).padStart(2, '0'); const yyyy = d.getFullYear(); if (!withTime) return `${dd}-${mm}-${yyyy}`; const hh = String(d.getHours()).padStart(2, '0'); const mi = String(d.getMinutes()).padStart(2, '0'); return `${dd}-${mm}-${yyyy} ${hh}:${mi}`; }; const columns = useMemo[]>(() => [ { accessorKey: 'id', header: 'ID', size: 80 }, { accessorKey: 'supplier.name', header: 'Supplier', size: 150 }, { accessorKey: 'tracking_number', header: 'Tracking #', size: 120 }, { accessorKey: 'arrival_date', header: 'Arrival Date', size: 120, Cell: ({ cell }) => formatDate(cell.getValue() || '', false), }, { accessorFn: row => row.files?.length ?? 0, id: 'files', header: 'Files', size: 80, }, { accessorKey: 'created_at', header: 'Created At', size: 150, Cell: ({ cell }) => formatDate(cell.getValue() || '', true), }, { accessorKey: 'updated_at', header: 'Updated At', size: 150, Cell: ({ cell }) => formatDate(cell.getValue() || '', true), }, ], []); useEffect(() => { fetchSuppliers(); }, []); useEffect(() => { fetchData(); }, [pagination.pageIndex, pagination.pageSize, sorting, globalFilter]); async function fetchSuppliers() { try { const res = await axios.get('/api/stockBatches/options'); setSuppliers(res.data.suppliers); } catch { toast.error('Failed to load suppliers'); } } async function fetchData() { setIsLoading(true); try { const res = await axios.get('/api/stockBatches', { params: { page: pagination.pageIndex + 1, perPage: pagination.pageSize, sortField: sorting[0]?.id, sortOrder: sorting[0]?.desc ? 'desc' : 'asc', filter: globalFilter, }, }); setData(res.data.data); console.log(res.data.data); setRowCount(res.data.meta.total); } catch { toast.error('Failed to load batches'); } finally { setIsLoading(false); } } function openModal() { dialogRef.current?.showModal(); } function closeModal() { dialogRef.current?.close(); setFormData(defaultFormData); setFiles([]); } const handleInputChange = ( e: React.ChangeEvent ) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: name === 'supplierId' ? value ? parseInt(value) : null : value, })); }; const handleFileChange = (e: React.ChangeEvent) => { if (e.target.files) { setFiles( Array.from(e.target.files).map(file => ({ file, fileType: 'other' as const, })) ); } }; const handleFileTypeChange = ( index: number, fileType: FileWithType['fileType'] ) => { setFiles(prev => prev.map((f, i) => (i === index ? { ...f, fileType } : f)) ); }; const removeFile = (index: number) => { setFiles(prev => prev.filter((_, i) => i !== index)); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const fd = new FormData(); if (formData.supplierId !== null) { fd.append('supplier_id', formData.supplierId.toString()); } if (formData.tracking_number) { fd.append('tracking_number', formData.tracking_number); } if (formData.arrival_date) { fd.append('arrival_date', formData.arrival_date); } files.forEach(({ file, fileType }, i) => { fd.append(`files[${i}]`, file); fd.append(`file_types[${i}]`, fileType); }); await axios.post('/api/stockBatches', fd, { headers: { 'Content-Type': 'multipart/form-data' }, }); toast.success('Batch created'); closeModal(); fetchData(); // inside your catch block… } catch (error: any) { if (axios.isAxiosError(error)) { // if it’s a 422 validation error if (error.response?.status === 422 && error.response.data.errors) { const errs = error.response.data.errors as Record; // flat-map all the messages and toast each one Object.values(errs) .flat() .forEach(msg => toast.error(msg, { duration: 5000, })); } else { // some other HTTP error toast.error(error.response?.statusText || 'Unknown error', { duration: 5000, }); toast.error(error.response?.data?.errors || '', { duration: 5000, }); } } else { // non-Axios error toast.error('Failed to create batch'); } } }; return ( (

Stock Batches

)} >

Batches

New Stock Batch

{files.map((f, idx) => (
{f.file.name}
))}
); }