diff --git a/app/add/page.js b/app/add/page.js index c6cb083..1e7f828 100644 --- a/app/add/page.js +++ b/app/add/page.js @@ -5,7 +5,7 @@ import { Upload, Plus, AlertCircle, CheckCircle2 } from "lucide-react"; import * as xlsx from "xlsx"; export default function AddImportPage() { - const [formData, setFormData] = useState({ name: "", qrCodeId: "", quantity: 0, price: 0.0, category: "", description: "" }); + const [formData, setFormData] = useState({ name: "", qrCodeId: "", quantity: 0, price: 0.0, category: "", location: "", description: "" }); const [loading, setLoading] = useState(false); const [status, setStatus] = useState({ type: "", message: "" }); const [importStats, setImportStats] = useState(null); @@ -37,7 +37,7 @@ export default function AddImportPage() { if (!res.ok) throw new Error(data.error || "Failed to create item"); setStatus({ type: "success", message: `Successfully added ${data.name}.` }); - setFormData({ name: "", qrCodeId: "", quantity: 0, price: 0.0, category: "", description: "" }); + setFormData({ name: "", qrCodeId: "", quantity: 0, price: 0.0, category: "", location: "", description: "" }); } catch (err) { setStatus({ type: "error", message: err.message }); } finally { @@ -81,6 +81,7 @@ export default function AddImportPage() { quantity: parseInt(normalizedRow["quantity"] || normalizedRow["qty"]) || 0, price: parseFloat(normalizedRow["price"] || normalizedRow["cost"]) || 0.0, category: normalizedRow["category"] || "", + location: normalizedRow["location"] || normalizedRow["bin"] || normalizedRow["aisle"] || "", description: normalizedRow["description"] || "", }; @@ -147,9 +148,15 @@ export default function AddImportPage() { -
- - +
+
+ + +
+
+ + +
diff --git a/app/api/inventory/route.js b/app/api/inventory/route.js index 8771f10..773df2b 100644 --- a/app/api/inventory/route.js +++ b/app/api/inventory/route.js @@ -17,7 +17,7 @@ export async function GET() { export async function POST(req) { try { const body = await req.json(); - const { qrCodeId, name, quantity, price, category, description } = body; + const { qrCodeId, name, quantity, price, category, location, description } = body; const newItem = await prisma.inventoryItem.upsert({ where: { qrCodeId }, @@ -26,6 +26,7 @@ export async function POST(req) { quantity: quantity || 0, price: price || 0.0, category: category || "", + location: location || "", description: description || "", }, create: { @@ -34,6 +35,7 @@ export async function POST(req) { quantity: quantity || 0, price: price || 0.0, category: category || "", + location: location || "", description: description || "", }, }); diff --git a/app/page.js b/app/page.js index 520a4c9..1b7bc35 100644 --- a/app/page.js +++ b/app/page.js @@ -11,6 +11,11 @@ export default function Dashboard() { const [editingId, setEditingId] = useState(null); const [editForm, setEditForm] = useState({}); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedCategory, setSelectedCategory] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 10; + const fetchItems = async () => { setLoading(true); setError(null); @@ -46,16 +51,18 @@ export default function Dashboard() { const startEdit = (item) => { setEditingId(item.id); - setEditForm({ name: item.name, category: item.category || "", quantity: item.quantity, price: item.price }); + setEditForm({ name: item.name, category: item.category || "", location: item.location || "", quantity: item.quantity, price: item.price }); }; const saveEdit = async (id) => { try { + const parsedQty = parseInt(editForm.quantity, 10); const payload = { id, name: editForm.name, category: editForm.category, - quantity: parseInt(editForm.quantity) || 0, + location: editForm.location, + quantity: isNaN(parsedQty) ? 0 : parsedQty, price: parseFloat(editForm.price) || 0.0, }; @@ -78,15 +85,35 @@ export default function Dashboard() { }; + // Filter and Paginate Items + useEffect(() => { + setCurrentPage(1); + }, [searchQuery, selectedCategory]); + + const uniqueCategories = [...new Set(items.map(item => item.category?.trim() || "Uncategorized"))].filter(c => c).sort(); + + const filteredItems = items.filter(item => { + const matchesSearch = item.name.toLowerCase().includes(searchQuery.toLowerCase()) || + item.qrCodeId.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesCategory = selectedCategory === "" || (item.category?.trim() || "Uncategorized") === selectedCategory; + return matchesSearch && matchesCategory; + }); + + const totalPages = Math.max(1, Math.ceil(filteredItems.length / itemsPerPage)); + const startIndex = (currentPage - 1) * itemsPerPage; + const paginatedItems = filteredItems.slice(startIndex, startIndex + itemsPerPage); + const handleExport = () => { - if (items.length === 0) return alert("No items to export"); - const ws = xlsx.utils.json_to_sheet(items.map(i => ({ + if (filteredItems.length === 0) return alert("No items to export"); + const ws = xlsx.utils.json_to_sheet(filteredItems.map(i => ({ "QR ID": i.qrCodeId, "Name": i.name, "Quantity": i.quantity, "Price": i.price, "Category": i.category || "", - "Description": i.description || "" + "Location": i.location || "", + "Description": i.description || "", + "Last Updated": i.updatedAt ? new Date(i.updatedAt).toLocaleString() : "" }))); const wb = xlsx.utils.book_new(); xlsx.utils.book_append_sheet(wb, ws, "Inventory"); @@ -133,19 +160,49 @@ export default function Dashboard() { ) : (
+
+ setSearchQuery(e.target.value)} + style={{ flex: 1 }} + /> + +
+ + + - {items.map(item => ( + {paginatedItems.length === 0 ? ( + + + + ) : paginatedItems.map(item => ( + +
Name QR ID CategoryLocation Quantity PriceLast Updated Actions
+ No items match your filters. +
{editingId === item.id ? ( @@ -158,6 +215,11 @@ export default function Dashboard() { setEditForm({...editForm, category: e.target.value})} /> ) : (item.category || "-")} + {editingId === item.id ? ( + setEditForm({...editForm, location: e.target.value})} /> + ) : (item.location || "-")} + {editingId === item.id ? ( setEditForm({...editForm, quantity: e.target.value})} /> @@ -172,6 +234,9 @@ export default function Dashboard() { setEditForm({...editForm, price: e.target.value})} /> ) : `$${Number(item.price).toFixed(2)}`} + {item.updatedAt ? new Date(item.updatedAt).toLocaleDateString() + " " + new Date(item.updatedAt).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : "N/A"} +
{editingId === item.id ? ( @@ -199,6 +264,33 @@ export default function Dashboard() { ))}
+ + {filteredItems.length > 0 && ( +
+ + Showing {startIndex + 1} to {Math.min(startIndex + itemsPerPage, filteredItems.length)} of {filteredItems.length} entries + +
+ + + Page {currentPage} of {totalPages} + + +
+
+ )}
)} diff --git a/app/print/page.js b/app/print/page.js index 8bdab6b..43f4d16 100644 --- a/app/print/page.js +++ b/app/print/page.js @@ -65,24 +65,28 @@ export default function PrintPage() { {/* Printable Sheet Area */} -
- {items.map((item, index) => ( -
-
- + {/* Printable Sheet Area */} + {Array.from({ length: Math.ceil(items.length / 30) }, (_, pageIndex) => ( +
+ {items.slice(pageIndex * 30, (pageIndex + 1) * 30).map((item) => ( +
+
+ +
+
+
{item.name}
+
{item.qrCodeId}
+
-
-
{item.name}
-
{item.qrCodeId}
-
-
- ))} -
+ ))} +
+ ))} +