269 lines
10 KiB
TypeScript
269 lines
10 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Modal } from '../Modal';
|
|
import { Input } from '../Input';
|
|
import { Button } from '../Button';
|
|
import { Select } from '../Select';
|
|
import { stockService, type StockItem } from '../../services/stockService';
|
|
import api from '../../services/api';
|
|
import { useAuth } from '../../context/useAuth';
|
|
import { useToast } from '../../hooks/useToast';
|
|
|
|
interface StockModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onSuccess: () => void;
|
|
initialData?: StockItem;
|
|
initialType?: 'PAINT' | 'THINNER';
|
|
}
|
|
|
|
export const StockModal: React.FC<StockModalProps> = ({ isOpen, onClose, onSuccess, initialData, initialType = 'PAINT' }) => {
|
|
const { isGuest } = useAuth();
|
|
const { showGuestWarning } = useToast();
|
|
const [loading, setLoading] = useState(false);
|
|
const [dataSheets, setDataSheets] = useState<any[]>([]);
|
|
|
|
// Form Data
|
|
const [dataSheetId, setDataSheetId] = useState('');
|
|
const [rrNumber, setRrNumber] = useState('');
|
|
const [batchNumber, setBatchNumber] = useState('');
|
|
const [color, setColor] = useState('');
|
|
const [invoiceNumber, setInvoiceNumber] = useState('');
|
|
const [receivedBy, setReceivedBy] = useState('');
|
|
const [quantity, setQuantity] = useState('');
|
|
const [unit, setUnit] = useState('L');
|
|
const [expirationDate, setExpirationDate] = useState('');
|
|
const [minStock, setMinStock] = useState('');
|
|
const [notes, setNotes] = useState('');
|
|
|
|
useEffect(() => {
|
|
const fetchDataSheets = async () => {
|
|
try {
|
|
const res = await api.get('/datasheets'); // Assuming this endpoint exists and lists all
|
|
setDataSheets(res.data);
|
|
} catch (err) {
|
|
console.error("Error fetching datasheets", err);
|
|
}
|
|
};
|
|
|
|
if (isOpen) {
|
|
fetchDataSheets();
|
|
if (initialData) {
|
|
setDataSheetId(typeof initialData.dataSheetId === 'object' ? initialData.dataSheetId._id : initialData.dataSheetId);
|
|
setRrNumber(initialData.rrNumber);
|
|
setBatchNumber(initialData.batchNumber);
|
|
setColor(initialData.color || '');
|
|
setInvoiceNumber(initialData.invoiceNumber || '');
|
|
setReceivedBy(initialData.receivedBy || '');
|
|
setQuantity(String(initialData.quantity));
|
|
setUnit(initialData.unit);
|
|
setExpirationDate(initialData.expirationDate ? new Date(initialData.expirationDate).toISOString().split('T')[0] : '');
|
|
setMinStock(String(initialData.minStock || 0));
|
|
setNotes(initialData.notes || '');
|
|
} else {
|
|
// Reset form
|
|
setDataSheetId('');
|
|
setRrNumber('');
|
|
setBatchNumber('');
|
|
setColor('');
|
|
setInvoiceNumber('');
|
|
setReceivedBy('');
|
|
setQuantity('');
|
|
setUnit('L');
|
|
setExpirationDate('');
|
|
setMinStock('0');
|
|
setNotes('');
|
|
}
|
|
}
|
|
}, [isOpen, initialData]);
|
|
|
|
// Handle filling color etc if picking a DataSheet (Optional feature, not implemented yet)
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
if (isGuest()) {
|
|
showGuestWarning();
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
|
|
const payload: any = {
|
|
dataSheetId,
|
|
rrNumber,
|
|
batchNumber,
|
|
color,
|
|
invoiceNumber,
|
|
receivedBy,
|
|
unit,
|
|
expirationDate: expirationDate || undefined,
|
|
minStock: Number(minStock) || 0,
|
|
notes
|
|
};
|
|
|
|
// If creating, send quantity. If updating, DO NOT send quantity (handled via adjusts)
|
|
if (!initialData) {
|
|
payload.quantity = Number(quantity);
|
|
}
|
|
|
|
try {
|
|
if (initialData) {
|
|
await stockService.update(initialData._id!, payload);
|
|
} else {
|
|
await stockService.create(payload);
|
|
}
|
|
onSuccess();
|
|
} catch (error: any) {
|
|
console.error('Error saving stock item:', error);
|
|
alert(error.response?.data?.error || 'Erro ao salvar item.');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
|
|
const isThinner = initialData
|
|
? (typeof initialData.dataSheetId === 'object' && (initialData.dataSheetId.type === 'THINNER' || initialData.dataSheetId.type === 'DILUENTE'))
|
|
: (initialType === 'THINNER');
|
|
|
|
const filteredDataSheets = dataSheets.filter(ds => {
|
|
const dsType = ds.type || 'PAINT';
|
|
const isDsThinner = dsType === 'THINNER' || dsType === 'DILUENTE';
|
|
return isThinner ? isDsThinner : !isDsThinner;
|
|
});
|
|
|
|
return (
|
|
<Modal
|
|
isOpen={isOpen}
|
|
onClose={onClose}
|
|
title={initialData ? "Editar Detalhes do Lote" : `Nova Entrada de Estoque (${isThinner ? 'Diluente' : 'Tinta'})`}
|
|
>
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<Select
|
|
label="Produto (Ficha Técnica)"
|
|
name="dataSheetId"
|
|
value={dataSheetId}
|
|
onChange={(e) => {
|
|
const val = e.target.value;
|
|
setDataSheetId(val);
|
|
// Auto-fill minStock from DataSheet if set and current is empty/0
|
|
const ds = dataSheets.find(d => d._id === val);
|
|
if (ds && ds.minStock && (!minStock || minStock === '0')) {
|
|
setMinStock(String(ds.minStock));
|
|
}
|
|
}}
|
|
options={filteredDataSheets.map(ds => ({ label: `${ds.name} - ${ds.manufacturer}`, value: ds._id }))}
|
|
disabled={!!initialData} // Lock product on edit
|
|
/>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<Input
|
|
label="RR (Rastreabilidade)"
|
|
name="rrNumber"
|
|
value={rrNumber}
|
|
onChange={(e) => setRrNumber(e.target.value)}
|
|
required
|
|
disabled={!!initialData} // Usually unique ID shouldn't change easily
|
|
/>
|
|
<Input
|
|
label="Lote Fabricante"
|
|
name="batchNumber"
|
|
value={batchNumber}
|
|
onChange={(e) => setBatchNumber(e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<Input
|
|
label="Nota Fiscal"
|
|
name="invoiceNumber"
|
|
value={invoiceNumber}
|
|
onChange={(e) => setInvoiceNumber(e.target.value)}
|
|
/>
|
|
<Input
|
|
label="Recebido Por"
|
|
name="receivedBy"
|
|
value={receivedBy}
|
|
onChange={(e) => setReceivedBy(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
{!isThinner && (
|
|
<Input
|
|
label="Cor"
|
|
name="color"
|
|
value={color}
|
|
onChange={(e) => setColor(e.target.value)}
|
|
placeholder="Ex: Amarelo Segurança, CINZA N6.5"
|
|
/>
|
|
)}
|
|
|
|
{!initialData && (
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<Input
|
|
label="Quantidade Inicial"
|
|
name="quantity"
|
|
type="number"
|
|
value={quantity}
|
|
onChange={(e) => setQuantity(e.target.value)}
|
|
required
|
|
/>
|
|
<Select
|
|
label="Unidade"
|
|
name="unit"
|
|
value={unit}
|
|
onChange={(e) => setUnit(e.target.value)}
|
|
options={[
|
|
{ label: 'Litros (L)', value: 'L' },
|
|
{ label: 'Galões (Gal)', value: 'Gal' },
|
|
{ label: 'Quartos (Qt)', value: 'Qt' },
|
|
{ label: 'Kg', value: 'Kg' },
|
|
{ label: 'Unidade (Un)', value: 'Un' }
|
|
]}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{!isThinner && (
|
|
<Input
|
|
label="Data de Validade"
|
|
name="expirationDate"
|
|
type="date"
|
|
value={expirationDate}
|
|
onChange={(e) => setExpirationDate(e.target.value)}
|
|
/>
|
|
)}
|
|
<div className={isThinner ? "col-span-2" : ""}>
|
|
<Input
|
|
label="Estoque Mínimo (L)"
|
|
name="minStock"
|
|
type="number"
|
|
value={minStock}
|
|
onChange={(e) => setMinStock(e.target.value)}
|
|
placeholder="Qtd de alerta"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<Input
|
|
label="Observações"
|
|
name="notes"
|
|
value={notes}
|
|
onChange={(e) => setNotes(e.target.value)}
|
|
/>
|
|
|
|
<div className="flex justify-end gap-2 mt-6">
|
|
<Button type="button" variant="ghost" onClick={onClose} disabled={loading}>
|
|
Cancelar
|
|
</Button>
|
|
<Button type="submit" disabled={loading}>
|
|
{loading ? 'Salvando...' : 'Salvar'}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</Modal >
|
|
);
|
|
};
|