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="add
new">+ 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>



Please be patient with us as Gen3Vintage transitions into an online format. All of our unique stock will be available completely online in our store and on various other platforms soon- and with free shipping!

Text Until then, your can still find us on Facebook! Just tap the link below