vat_wms/resources/js/Pages/StockBatch_old.tsx
2025-06-02 07:36:24 +02:00

370 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<StockBatch[]>([]);
const [suppliers, setSuppliers] = useState<Supplier[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [rowCount, setRowCount] = useState(0);
const [pagination, setPagination] = useState<MRT_PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const [sorting, setSorting] = useState<MRT_SortingState>([
{ id: 'updatedAt', desc: true },
]);
const [globalFilter, setGlobalFilter] = useState('');
const dialogRef = useRef<HTMLDialogElement>(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<FileWithType[]>([]);
// 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<MRT_ColumnDef<StockBatch>[]>(() => [
{ 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<string>() || '', 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<string>() || '', true),
},
{
accessorKey: 'updated_at',
header: 'Updated At',
size: 150,
Cell: ({ cell }) => formatDate(cell.getValue<string>() || '', 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<HTMLInputElement | HTMLSelectElement>
) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]:
name === 'supplierId'
? value
? parseInt(value)
: null
: value,
}));
};
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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 its a 422 validation error
if (error.response?.status === 422 && error.response.data.errors) {
const errs = error.response.data.errors as Record<string, string[]>;
// 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 (
<AppLayout
title="Stock Batches"
renderHeader={() => (
<h2 className="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
Stock Batches
</h2>
)}
>
<Head title="Stock Batches" />
<Toaster
position="top-center"
reverseOrder={false}
/>
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="bg-white dark:bg-gray-800 overflow-hidden shadow-xl sm:rounded-lg p-6">
<div className="mb-4 flex justify-between items-center">
<h1 className="text-2xl font-bold">Batches</h1>
<button onClick={openModal} className="btn btn-primary">
Add New Batch
</button>
</div>
<MaterialReactTable
columns={columns}
data={data}
manualPagination
manualSorting
enableGlobalFilter
onPaginationChange={setPagination}
onSortingChange={setSorting}
onGlobalFilterChange={setGlobalFilter}
rowCount={rowCount}
state={{ isLoading, pagination, sorting, globalFilter }}
/>
</div>
</div>
</div>
<dialog ref={dialogRef} className="modal">
<form onSubmit={handleSubmit} className="modal-box p-6">
<button
type="button"
onClick={closeModal}
className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
>
</button>
<h3 className="font-bold text-lg mb-4">New Stock Batch</h3>
<div className="form-control mb-4">
<label htmlFor="supplierId" className="label">
<span className="label-text">Supplier</span>
</label>
<select
id="supplierId"
name="supplierId"
value={formData.supplierId ?? ''}
onChange={handleInputChange}
className="select"
>
<option value="">Select supplier...</option>
{suppliers.map(s => (
<option key={s.id} value={s.id}>
{s.name}
</option>
))}
</select>
</div>
<div className="form-control mb-4">
<label htmlFor="tracking_number" className="label">
<span className="label-text">Tracking Number</span>
</label>
<input
id="tracking_number"
name="tracking_number"
type="text"
value={formData.tracking_number}
onChange={handleInputChange}
className="input"
/>
</div>
<div className="form-control mb-4">
<label htmlFor="arrival_date" className="label">
<span className="label-text">Arrival Date</span>
</label>
<input
id="arrival_date"
name="arrival_date"
type="date"
value={formData.arrival_date}
onChange={handleInputChange}
className="input"
/>
</div>
<div className="form-control mb-4">
<label htmlFor="files" className="label">
<span className="label-text">Upload Files</span>
</label>
<input
id="files"
type="file"
multiple
onChange={handleFileChange}
className="file-input"
/>
</div>
{files.map((f, idx) => (
<div key={idx} className="flex items-center mb-2 space-x-2">
<span>{f.file.name}</span>
<select
value={f.fileType}
onChange={e =>
handleFileTypeChange(idx, e.target.value as FileWithType['fileType'])
}
className="select select-sm"
>
<option value="invoice">Invoice</option>
<option value="label">Label</option>
<option value="other">Other</option>
</select>
<button
type="button"
onClick={() => removeFile(idx)}
className="btn btn-sm btn-error"
>
Remove
</button>
</div>
))}
<div className="modal-action">
<button type="submit" className="btn btn-primary">
Create Batch
</button>
</div>
</form>
</dialog>
</AppLayout>
);
}