Upload source code
This commit is contained in:
268
src/client/components/modals/StockModal.tsx
Normal file
268
src/client/components/modals/StockModal.tsx
Normal file
@@ -0,0 +1,268 @@
|
||||
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 >
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user