added location field
This commit is contained in:
104
app/page.js
104
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() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="table-container mt-4">
|
||||
<div className="flex gap-4 mb-4" style={{ alignItems: "center" }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by name or QR..."
|
||||
className="input-field"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<select
|
||||
className="input-field"
|
||||
value={selectedCategory}
|
||||
onChange={(e) => setSelectedCategory(e.target.value)}
|
||||
style={{ width: "200px" }}
|
||||
>
|
||||
<option value="">All Categories</option>
|
||||
{uniqueCategories.map(cat => (
|
||||
<option key={cat} value={cat}>{cat}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<table className="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>QR ID</th>
|
||||
<th>Category</th>
|
||||
<th>Location</th>
|
||||
<th>Quantity</th>
|
||||
<th>Price</th>
|
||||
<th>Last Updated</th>
|
||||
<th style={{ textAlign: "right" }}>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map(item => (
|
||||
{paginatedItems.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan="8" style={{ textAlign: "center", padding: "2rem", color: "var(--text-muted)" }}>
|
||||
No items match your filters.
|
||||
</td>
|
||||
</tr>
|
||||
) : paginatedItems.map(item => (
|
||||
<tr key={item.id}>
|
||||
<td style={{ fontWeight: 500 }}>
|
||||
{editingId === item.id ? (
|
||||
@ -158,6 +215,11 @@ export default function Dashboard() {
|
||||
<input className="input-field" style={{ padding: "0.2rem 0.5rem", width: "100%" }} value={editForm.category} onChange={e => setEditForm({...editForm, category: e.target.value})} />
|
||||
) : (item.category || "-")}
|
||||
</td>
|
||||
<td>
|
||||
{editingId === item.id ? (
|
||||
<input className="input-field" style={{ padding: "0.2rem 0.5rem", width: "100%" }} value={editForm.location} onChange={e => setEditForm({...editForm, location: e.target.value})} />
|
||||
) : (item.location || "-")}
|
||||
</td>
|
||||
<td>
|
||||
{editingId === item.id ? (
|
||||
<input type="number" className="input-field" style={{ padding: "0.2rem 0.5rem", width: "80px" }} value={editForm.quantity} onChange={e => setEditForm({...editForm, quantity: e.target.value})} />
|
||||
@ -172,6 +234,9 @@ export default function Dashboard() {
|
||||
<input type="number" step="0.01" className="input-field" style={{ padding: "0.2rem 0.5rem", width: "80px" }} value={editForm.price} onChange={e => setEditForm({...editForm, price: e.target.value})} />
|
||||
) : `$${Number(item.price).toFixed(2)}`}
|
||||
</td>
|
||||
<td className="text-muted" style={{ fontSize: "0.85rem" }}>
|
||||
{item.updatedAt ? new Date(item.updatedAt).toLocaleDateString() + " " + new Date(item.updatedAt).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : "N/A"}
|
||||
</td>
|
||||
<td style={{ textAlign: "right" }}>
|
||||
<div className="flex" style={{ gap: "0.5rem", justifyContent: "flex-end" }}>
|
||||
{editingId === item.id ? (
|
||||
@ -199,6 +264,33 @@ export default function Dashboard() {
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{filteredItems.length > 0 && (
|
||||
<div className="flex justify-between items-center mt-4" style={{ padding: "1rem 0 0 0" }}>
|
||||
<span className="text-muted" style={{ fontSize: "0.9rem" }}>
|
||||
Showing {startIndex + 1} to {Math.min(startIndex + itemsPerPage, filteredItems.length)} of {filteredItems.length} entries
|
||||
</span>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<span className="flex items-center text-muted" style={{ padding: "0 0.5rem", fontSize: "0.9rem" }}>
|
||||
Page {currentPage} of {totalPages}
|
||||
</span>
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user