import React, { useState, useEffect } from 'react';
import { Upload, Download, Plus, Trash2, X, Tag } from 'lucide-react';export default function InventoryManager() {
const [items, setItems] = useState([]);
const [categories, setCategories] = useState(['Electronics', 'Furniture', 'Clothing', 'Books', 'Other']);
const [showForm, setShowForm] = useState(false);
const [currentItem, setCurrentItem] = useState({
name: '',
description: '',
category: '',
originalCost: '',
resellPrice: '',
image: null,
imageUrl: ''
});
const [newCategory, setNewCategory] = useState('');
const [showCategoryInput, setShowCategoryInput] = useState(false);
const [importError, setImportError] = useState('');useEffect(() => {
const saved = localStorage.getItem('inventoryItems');
const savedCategories = localStorage.getItem('inventoryCategories');
if (saved) setItems(JSON.parse(saved));
if (savedCategories) setCategories(JSON.parse(savedCategories));
}, []);useEffect(() => {
localStorage.setItem('inventoryItems', JSON.stringify(items));
}, [items]);useEffect(() => {
localStorage.setItem('inventoryCategories', JSON.stringify(categories));
}, [categories]);const handleImageUpload = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setCurrentItem({ ...currentItem, image: file, imageUrl: reader.result });
};
reader.readAsDataURL(file);
}
};const addCategory = () => {
if (newCategory && !categories.includes(newCategory)) {
setCategories([...categories, newCategory]);
setCurrentItem({ ...currentItem, category: newCategory });
setNewCategory('');
setShowCategoryInput(false);
}
};const addItem = () => {
if (currentItem.name && currentItem.category) {
setItems([...items, { ...currentItem, id: Date.now() }]);
setCurrentItem({
name: '',
description: '',
category: '',
originalCost: '',
resellPrice: '',
image: null,
imageUrl: ''
});
setShowForm(false);
}
};const deleteItem = (id) => {
setItems(items.filter(item => item.id !== id));
};const exportToCSV = () => {
const headers = ['Name', 'Description', 'Category', 'Original Cost', 'Resell Price', 'Profit/Loss'];
const rows = items.map(item => {
const profit = (parseFloat(item.resellPrice) || 0) - (parseFloat(item.originalCost) || 0);
return [
item.name,
item.description,
item.category,
item.originalCost,
item.resellPrice,
profit.toFixed(2)
];
});const csvContent = [
headers.join(','),
...rows.map(row => row.map(cell => "${cell}").join(','))
].join('
');const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = inventory-${new Date().toISOString().split('T')[0]}.csv;
a.click();
};const exportToJSON = () => {
const dataStr = JSON.stringify(items, null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = inventory-${new Date().toISOString().split('T')[0]}.json;
a.click();
};const parseCSV = (text) => {
const lines = text.split('
').filter(line => line.trim());
if (lines.length < 2) return [];const headers = lines[0].split(',').map(h => h.replace(/"/g, '').trim().toLowerCase());
const items = [];for (let i = 1; i < lines.length; i++) {
const values = [];
let current = '';
let inQuotes = false;for (let char of lines[i]) {
if (char === '"') {
inQuotes = !inQuotes;
} else if (char === ',' && !inQuotes) {
values.push(current.trim());
current = '';
} else {
current += char;
}
}
values.push(current.trim());const item = {
id: Date.now() + i,
name: '',
description: '',
category: '',
originalCost: '',
resellPrice: '',
image: null,
imageUrl: ''
};headers.forEach((header, index) => {
const value = values[index] || '';
if (header.includes('name')) item.name = value;
else if (header.includes('description')) item.description = value;
else if (header.includes('category')) item.category = value;
else if (header.includes('original') && header.includes('cost')) item.originalCost = value;
else if (header.includes('resell') && header.includes('price')) item.resellPrice = value;
});if (item.name) items.push(item);
}return items;
};const handleFileImport = (e) => {
const file = e.target.files[0];
if (!file) return;setImportError('');
const reader = new FileReader();reader.onload = (event) => {
try {
const content = event.target.result;
let importedItems = [];if (file.name.endsWith('.json')) {
const parsed = JSON.parse(content);
importedItems = Array.isArray(parsed) ? parsed : [parsed];// Ensure imported items have required structure
importedItems = importedItems.map((item, index) => ({
id: Date.now() + index,
name: item.name || '',
description: item.description || '',
category: item.category || '',
originalCost: item.originalCost || '',
resellPrice: item.resellPrice || '',
image: null,
imageUrl: item.imageUrl || ''
}));
} else if (file.name.endsWith('.csv')) {
importedItems = parseCSV(content);
} else {
setImportError('Please upload a CSV or JSON file');
return;
}if (importedItems.length === 0) {
setImportError('No valid items found in file');
return;
}// Extract and add new categories
const newCategories = [...new Set(importedItems.map(item => item.category).filter(Boolean))];
const updatedCategories = [...new Set([...categories, ...newCategories])];
setCategories(updatedCategories);// Merge with existing items
setItems([...items, ...importedItems]);alert(Successfully imported ${importedItems.length} items!);
} catch (error) {
setImportError('Error parsing file: ' + error.message);
}
};reader.readAsText(file);
e.target.value = ''; // Reset input
};const totalOriginalCost = items.reduce((sum, item) => sum + (parseFloat(item.originalCost) || 0), 0);
const totalResellValue = items.reduce((sum, item) => sum + (parseFloat(item.resellPrice) || 0), 0);
const totalProfit = totalResellValue - totalOriginalCost;return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50 p-6">
<div className="max-w-7xl mx-auto">
<div className="bg-white rounded-2xl shadow-xl p-8 mb-6">
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-3xl font-bold text-gray-800">Inventory Manager</h1>
<p className="text-gray-600 mt-1">Track your items, costs, and resale values</p>
</div>
<button
onClick={() => setShowForm(true)}
className="bg-indigo-600 text-white px-6 py-3 rounded-lg hover:bg-indigo-700 transition flex items-center gap-2 shadow-md"
>
<Plus size={20} /> Add Item
</button>
</div><div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div className="bg-blue-50 p-4 rounded-lg border border-blue-200">
<p className="text-sm text-blue-600 font-semibold">Total Items</p>
<p className="text-2xl font-bold text-blue-900">{items.length}</p>
</div>
<div className="bg-green-50 p-4 rounded-lg border border-green-200">
<p className="text-sm text-green-600 font-semibold">Total Original Cost</p>
<p className="text-2xl font-bold text-green-900">${totalOriginalCost.toFixed(2)}</p>
</div>
<div className={${totalProfit >= 0 ? 'bg-emerald-50 border-emerald-200' : 'bg-red-50 border-red-200'} p-4 rounded-lg border}>
<p className={text-sm font-semibold ${totalProfit >= 0 ? 'text-emerald-600' : 'text-red-600'}}>
Projected Profit/Loss
</p>
<p className={text-2xl font-bold ${totalProfit >= 0 ? 'text-emerald-900' : 'text-red-900'}}>
${totalProfit.toFixed(2)}
</p>
</div>
</div><div className="flex gap-3 mb-6 flex-wrap">
<input
type="file"
accept=".csv,.json"
onChange={handleFileImport}
className="hidden"
id="fileImport"
/>
<label
htmlFor="fileImport"
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition flex items-center gap-2 cursor-pointer"
>
<Upload size={18} /> Import CSV/JSON
</label>
<button
onClick={exportToCSV}
className="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition flex items-center gap-2"
>
<Download size={18} /> Export CSV
</button>
<button
onClick={exportToJSON}
className="bg-purple-600 text-white px-4 py-2 rounded-lg hover:bg-purple-700 transition flex items-center gap-2"
>
<Download size={18} /> Export JSON
</button>
</div>{importError && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg mb-6">
{importError}
</div>
)}
</div>{showForm && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto p-8">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-gray-800">Add New Item</h2>
<button onClick={() => setShowForm(false)} className="text-gray-500 hover:text-gray-700">
<X size={24} />
</button>
</div><div className="space-y-4">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Item Name *</label>
<input
type="text"
value={currentItem.name}
onChange={(e) => setCurrentItem({ ...currentItem, name: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
placeholder="Enter item name"
/>
</div><div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Description</label>
<textarea
value={currentItem.description}
onChange={(e) => setCurrentItem({ ...currentItem, description: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
placeholder="Enter item description"
rows="3"
/>
</div><div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Category *</label>
<select
value={currentItem.category}
onChange={(e) => {
if (e.target.value === 'addnew') {
setShowCategoryInput(true);
} else {
setCurrentItem({ ...currentItem, category: e.target.value });
}
}}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
>
<option value="">Select a category</option>
{categories.map(cat => (
<option key={cat} value={cat}>{cat}</option>
))}
<option value="addnew">+ Add New Category</option>
</select>{showCategoryInput && (
<div className="mt-3 flex gap-2">
<input
type="text"
value={newCategory}
onChange={(e) => setNewCategory(e.target.value)}
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
placeholder="Enter new category name"
/>
<button
onClick={addCategory}
className="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 transition"
>
Add
</button>
<button
onClick={() => setShowCategoryInput(false)}
className="bg-gray-300 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-400 transition"
>
Cancel
</button>
</div>
)}
</div><div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Original Cost ($)</label>
<input
type="number"
step="0.01"
value={currentItem.originalCost}
onChange={(e) => setCurrentItem({ ...currentItem, originalCost: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
placeholder="0.00"
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Resell Price ($)</label>
<input
type="number"
step="0.01"
value={currentItem.resellPrice}
onChange={(e) => setCurrentItem({ ...currentItem, resellPrice: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
placeholder="0.00"
/>
</div>
</div><div>
<label className="block text-sm font-semibold text-gray-700 mb-2">Item Image</label>
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center">
{currentItem.imageUrl ? (
<div className="relative">
<img src={currentItem.imageUrl} alt="Preview" className="max-h-48 mx-auto rounded mb-3" />
<button
type="button"
onClick={() => document.getElementById('imageUploadInput').click()}
className="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 transition text-sm"
>
Change Image
</button>
</div>
