159 lines
9.1 KiB
JavaScript
159 lines
9.1 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { X, RefreshCw, Calendar, MessageSquare, Info } from 'lucide-react';
|
|
|
|
export default function AdjustStockModal({ isOpen, onClose, onSave, product }) {
|
|
const [formData, setFormData] = useState({
|
|
adjustment_qty: '',
|
|
adjustment_date: new Date().toISOString().split('T')[0],
|
|
reason: 'Adjustment',
|
|
remarks: ''
|
|
});
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
if (!isOpen || !product) return null;
|
|
|
|
const handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
setLoading(true);
|
|
try {
|
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
|
const res = await fetch(`/api/inventory/products/${product.id}/adjust`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': csrfToken
|
|
},
|
|
body: JSON.stringify(formData)
|
|
});
|
|
if (res.ok) {
|
|
const updatedProduct = await res.json();
|
|
onSave(updatedProduct);
|
|
setFormData({
|
|
adjustment_qty: '',
|
|
adjustment_date: new Date().toISOString().split('T')[0],
|
|
reason: 'Adjustment',
|
|
remarks: ''
|
|
});
|
|
onClose();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error adjusting stock:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const newStockLevel = parseInt(product.current_stock) + parseInt(formData.adjustment_qty || 0);
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-[#00171F]/40 backdrop-blur-sm z-[999] flex items-center justify-center p-4">
|
|
<div className="bg-white rounded-[32px] w-full max-w-lg overflow-hidden shadow-2xl animate-in zoom-in-95 duration-300">
|
|
<div className="p-8 border-b border-gray-100 flex items-center justify-between bg-gray-50/50">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-12 h-12 bg-blue-50 rounded-2xl flex items-center justify-center text-blue-600 border border-blue-100/50">
|
|
<RefreshCw size={24} />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-2xl font-black text-gray-900 tracking-tight">Adjust Stock</h2>
|
|
<p className="text-xs text-gray-500 font-bold uppercase tracking-widest mt-0.5">{product.name}</p>
|
|
</div>
|
|
</div>
|
|
<button onClick={onClose} className="p-2 hover:bg-gray-100 rounded-xl transition-colors">
|
|
<X size={24} className="text-gray-400" />
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="p-8 space-y-6">
|
|
<div className="bg-blue-50/50 border border-blue-100/50 p-4 rounded-2xl flex items-center justify-between">
|
|
<div>
|
|
<p className="text-[10px] font-black text-blue-400 uppercase tracking-widest mb-1">Current Stock</p>
|
|
<p className="text-2xl font-black text-blue-600">{product.current_stock}</p>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-1">New Level</p>
|
|
<p className={`text-2xl font-black ${newStockLevel < 0 ? 'text-red-500' : 'text-gray-900'}`}>{newStockLevel}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Adjustment Qty (+ or -)</label>
|
|
<div className="relative">
|
|
<Plus className="absolute left-6 top-1/2 -translate-y-1/2 text-gray-300" size={18} />
|
|
<input
|
|
required type="number"
|
|
className="w-full pl-14 pr-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-blue-500/10 focus:border-blue-500 transition-all font-bold text-gray-900"
|
|
placeholder="e.g. 5 or -5"
|
|
value={formData.adjustment_qty}
|
|
onChange={e => setFormData({...formData, adjustment_qty: e.target.value})}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Adjustment Date</label>
|
|
<div className="relative">
|
|
<Calendar className="absolute left-6 top-1/2 -translate-y-1/2 text-gray-300" size={18} />
|
|
<input
|
|
required type="date"
|
|
className="w-full pl-14 pr-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-blue-500/10 focus:border-blue-500 transition-all font-bold text-gray-900"
|
|
value={formData.adjustment_date}
|
|
onChange={e => setFormData({...formData, adjustment_date: e.target.value})}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Reason</label>
|
|
<select
|
|
required
|
|
className="w-full max-w-full px-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-blue-500/10 focus:border-blue-500 transition-all font-bold text-gray-900 appearance-none truncate"
|
|
value={formData.reason}
|
|
onChange={e => setFormData({...formData, reason: e.target.value})}
|
|
>
|
|
<option value="Adjustment">Normal Adjustment</option>
|
|
<option value="Purchase">Stock Purchase</option>
|
|
<option value="Damage">Damaged Goods</option>
|
|
<option value="Return">Customer Return</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2 ml-1">Remarks</label>
|
|
<div className="relative">
|
|
<MessageSquare className="absolute left-6 top-6 text-gray-300" size={18} />
|
|
<textarea
|
|
className="w-full pl-14 pr-6 py-4 bg-gray-50/50 border border-gray-100 rounded-2xl outline-none focus:ring-2 focus:ring-blue-500/10 focus:border-blue-500 transition-all font-bold text-gray-900 min-h-[100px]"
|
|
placeholder="Internal notes..."
|
|
value={formData.remarks}
|
|
onChange={e => setFormData({...formData, remarks: e.target.value})}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-6 border-t border-gray-100 flex gap-4">
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="flex-1 py-4 bg-gray-50 text-gray-400 rounded-2xl font-black text-xs uppercase tracking-widest hover:bg-gray-100 transition-all"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
disabled={loading || !formData.adjustment_qty || newStockLevel < 0}
|
|
type="submit"
|
|
className="flex-1 py-4 bg-blue-600 text-white rounded-2xl font-black text-xs uppercase tracking-widest hover:bg-blue-700 transition-all shadow-lg shadow-blue-100 disabled:opacity-50"
|
|
>
|
|
{loading ? 'Adjusting...' : 'Update Stock'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
const Plus = ({ className, size }) => (
|
|
<svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
|
);
|