import React, { useState, useMemo, useEffect } from 'react'
import { Button } from "/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "/components/ui/card"
import { Input } from "/components/ui/input"
import { Label } from "/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { Search, Plus, Trash2, ArrowLeft, Check, Minus, ShoppingCart, RefreshCw, X, RotateCcw } from 'lucide-react'
interface Product {
id: string
name: string
quantity: number
isCompleted: boolean
isOutOfStock: boolean
imageUrl?: string
comment?: string
completedAt?: Date
}
interface RestockList {
id: string
name: string
description: string
createdAt: Date
products: Product[]
}
export default function GuluInventoryApp() {
const [lists, setLists] = useState<RestockList[]>(() => {
const saved = localStorage.getItem('gulu-lists')
return saved ? JSON.parse(saved).map((l: any) => ({
...l,
createdAt: new Date(l.createdAt),
products: l.products.map((p: any) => ({...p, completedAt: p.completedAt ? new Date(p.completedAt) : undefined}))
})) : [
{
id: '1',
name: 'Morning Fresh Produce',
description: 'Restock fresh fruits and vegetables for morning shift',
createdAt: new Date('2024-01-15'),
products: [
{ id: '1', name: 'Bananas', quantity: 20, isCompleted: false, isOutOfStock: false },
{ id: '2', name: 'Apples', quantity: 15, isCompleted: true, isOutOfStock: false, completedAt: new Date() },
{ id: '3', name: 'Carrots', quantity: 10, isCompleted: false, isOutOfStock: false }
]
}
]
})
const [selectedListId, setSelectedListId] = useState<string | null>(null)
const [searchQuery, setSearchQuery] = useState('')
const [searchProductName, setSearchProductName] = useState('')
const [sortBy, setSortBy] = useState<'name' | 'quantity' | 'status'>('status')
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc')
const [showNewListForm, setShowNewListForm] = useState(false)
const [showEditModal, setShowEditModal] = useState(false)
const [editingProduct, setEditingProduct] = useState<Product | null>(null)
const [newListName, setNewListName] = useState('')
const [newListDescription, setNewListDescription] = useState('')
const [newProductName, setNewProductName] = useState('')
const [newProductImage, setNewProductImage] = useState('')
const [newProductComment, setNewProductComment] = useState('')
const [editForm, setEditForm] = useState({name: '', imageUrl: '', comment: ''})
useEffect(() => {
localStorage.setItem('gulu-lists', JSON.stringify(lists))
}, [lists])
const selectedList = lists.find(list => list.id === selectedListId) || null
const filteredLists = useMemo(() => {
return lists.filter(list =>
list.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
list.description.toLowerCase().includes(searchQuery.toLowerCase())
).sort((a, b) => {
if (sortBy === 'name') {
return sortOrder === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name)
} else if (sortBy === 'quantity') {
return sortOrder === 'asc' ? a.products.length - b.products.length : b.products.length - a.products.length
} else {
return sortOrder === 'asc' ? a.createdAt.getTime() - b.createdAt.getTime() : b.createdAt.getTime() - a.createdAt.getTime()
}
})
}, [lists, searchQuery, sortBy, sortOrder])
const createNewList = () => {
if (newListName.trim()) {
const newList: RestockList = {
id: Date.now().toString(),
name: newListName,
description: newListDescription,
createdAt: new Date(),
products: []
}
setLists([...lists, newList])
setNewListName('')
setNewListDescription('')
setShowNewListForm(false)
}
}
const deleteList = (listId: string) => setLists(lists.filter(list => list.id !== listId))
const resetAllQuantities = () => {
if (!selectedList) return
const updatedList = {
...selectedList,
products: selectedList.products.map(p => ({
...p,
quantity: 0,
isCompleted: false,
completedAt: undefined
}))
}
setLists(lists.map(list => list.id === selectedList.id ? updatedList : list))
}
const resetProductQuantity = (productId: string) => {
if (!selectedList) return
const updatedList = {
...selectedList,
products: selectedList.products.map(p =>
p.id === productId ? {...p, quantity: 0} : p
)
}
setLists(lists.map(list => list.id === selectedList.id ? updatedList : list))
}
const updateProductQuantity = (productId: string, change: number) => {
if (!selectedList) return
const updatedList = {
...selectedList,
products: selectedList.products.map(p => p.id === productId ? {...p, quantity: Math.max(0, p.quantity + change)} : p)
}
setLists(lists.map(list => list.id === selectedList.id ? updatedList : list))
}
const toggleProductCompletion = (productId: string) => {
if (!selectedList) return
const product = selectedList.products.find(p => p.id === productId)
if (!product) return
const isNowCompleted = !product.isCompleted
const updatedList = {
...selectedList,
products: selectedList.products.map(p =>
p.id === productId
? { ...p, isCompleted: isNowCompleted, completedAt: isNowCompleted ? new Date() : undefined }
: p
)
}
setLists(lists.map(list => list.id === selectedList.id ? updatedList : list))
}
const toggleOutOfStock = (productId: string) => {
if (!selectedList) return
const updatedList = {
...selectedList,
products: selectedList.products.map(p =>
p.id === productId ? {...p, isOutOfStock: !p.isOutOfStock} : p
)
}
setLists(lists.map(list => list.id === selectedList.id ? updatedList : list))
}
const addProduct = () => {
if (!selectedList || !newProductName.trim()) return
const newProduct: Product = {
id: Date.now().toString(),
name: newProductName,
quantity: 0,
isCompleted: false,
isOutOfStock: false,
imageUrl: newProductImage || undefined,
comment: newProductComment || undefined
}
const updatedList = {...selectedList, products: [...selectedList.products, newProduct]}
setLists(lists.map(list => list.id === selectedList.id ? updatedList : list))
setNewProductName('')
setNewProductImage('')
setNewProductComment('')
}
const openEditModal = (product: Product) => {
setEditingProduct(product)
setEditForm({name: product.name, imageUrl: product.imageUrl || '', comment: product.comment || ''})
setShowEditModal(true)
}
const closeEditModal = () => {
setShowEditModal(false)
setEditingProduct(null)
}
const saveProductEdit = () => {
if (!editingProduct || !selectedList || !editForm.name.trim()) return
const updatedList = {
...selectedList,
products: selectedList.products.map(p =>
p.id === editingProduct.id ? {
...p,
name: editForm.name,
imageUrl: editForm.imageUrl || undefined,
comment: editForm.comment || undefined
} : p
)
}
setLists(lists.map(list => list.id === selectedList.id ? updatedList : list))
closeEditModal()
}
const deleteProductFromEdit = () => {
if (!editingProduct || !selectedList) return
const updatedList = {
...selectedList,
products: selectedList.products.filter(p => p.id !== editingProduct.id)
}
setLists(lists.map(list => list.id === selectedList.id ? updatedList : list))
closeEditModal()
}
// Main render logic
const renderContent = () => {
if (selectedList) {
// Create properly sorted products
const getSortedProducts = (products: Product[]) => {
let sorted = [...products]
// First filter by search
if (searchProductName) {
sorted = sorted.filter(p =>
p.name.toLowerCase().includes(searchProductName.toLowerCase())
)
}
// Then sort
if (sortBy === 'name') {
sorted.sort((a, b) =>
sortOrder === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name)
)
} else if (sortBy === 'quantity') {
sorted.sort((a, b) =>
sortOrder === 'asc' ? a.quantity - b.quantity : b.quantity - a.quantity
)
} else { // status
sorted.sort((a, b) => {
if (a.isCompleted !== b.isCompleted) {
return a.isCompleted ? 1 : -1
}
return a.name.localeCompare(b.name)
})
}
return sorted
}
const allProducts = getSortedProducts(selectedList.products)
const inStockProducts = allProducts.filter(p => !p.isOutOfStock)
const outOfStockProducts = allProducts.filter(p => p.isOutOfStock)
return (
<div className="min-h-screen bg-gray-50 p-4">
<div className="max-w-6xl mx-auto">
<div className="flex items-center justify-between mb-6">
<button
onClick={() => setSelectedListId(null)}
className="flex items-center gap-2 px-4 py-2 text-sm border border-slate-300 rounded-md hover:bg-slate-50 transition-colors"
>
<ArrowLeft className="w-4 h-4" />
Back to Lists
</button>
<div className="flex items-center gap-4">
<button
onClick={resetAllQuantities}
className="flex items-center gap-2 px-4 py-2 text-sm border border-orange-300 text-orange-600 rounded-md hover:bg-orange-50 transition-colors"
title="Reset all quantities to zero"
>
<RotateCcw className="w-4 h-4" />
Reset All
</button>
<div className="text-sm text-gray-600">
{selectedList.products.filter(p => p.isCompleted && !p.isOutOfStock).length} of {selectedList.products.filter(p => !p.isOutOfStock).length} items completed
</div>
</div>
</div>
<div className="mb-6">
<h1 className="text-3xl font-bold text-slate-800 mb-2">{selectedList.name}</h1>
<p className="text-slate-600">{selectedList.description}</p>
</div>
<div className="mb-6">
<div className="flex gap-2">
<Input
placeholder="Add new product..."
value={newProductName}
onChange={(e) => setNewProductName(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addProduct()}
className="max-w-md"
/>
<Input
placeholder="Image URL (optional)"
value={newProductImage}
onChange={(e) => setNewProductImage(e.target.value)}
className="max-w-md"
/>
<Input
placeholder="Comment (optional)"
value={newProductComment}
onChange={(e) => setNewProductComment(e.target.value)}
className="max-w-md"
/>
<button
onClick={addProduct}
style={{ backgroundColor: '#58ab7f', color: 'white' }}
className="px-4 py-2 rounded-md transition-colors"
>
<Plus className="w-4 h-4" />
</button>
</div>
</div>
<div className="flex flex-col sm:flex-row gap-4 mb-4">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400 w-4 h-4 pointer-events-none" />
<Input
placeholder="Search products..."
value={searchProductName}
onChange={(e) => setSearchProductName(e.target.value)}
className="pl-10"
/>
</div>
<div className="flex gap-2">
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value as 'name' | 'quantity' | 'status')}
className="px-3 py-2 border rounded-md bg-white"
>
<option value="status">Status</option>
<option value="name">Name</option>
<option value="quantity">Quantity</option>
</select>
<button
onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}
className="px-3 py-2 border rounded-md bg-white hover:bg-slate-50 transition-colors"
>
{sortOrder === 'asc' ? '↑' : '↓'}
</button>
</div>
</div>
{inStockProducts.length > 0 && (
<div className="mb-8">
<h2 className="text-xl font-semibold mb-4">In Stock ({inStockProducts.length})</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{inStockProducts.map(product => (
<div
key={product.id}
className="border-2 rounded-xl transition-all hover:shadow-md bg-white border-slate-200"
>
<div
className="p-4 cursor-pointer"
onClick={() => openEditModal(product)}
>
<div className="flex justify-between items-start mb-2">
<div className="flex items-center gap-2 flex-1">
<button
onClick={(e) => {
e.stopPropagation()
toggleOutOfStock(product.id)
}}
className="p-1 rounded-full hover:bg-gray-100 transition-colors"
title="Mark as out of stock"
>
<ShoppingCart className="w-4 h-4 text-green-500" />
</button>
<h3 className={`font-semibold ${product.isCompleted ? 'text-green-600 line-through' : 'text-slate-800'}`}>
{product.name}
</h3>
</div>
<button
onClick={(e) => {
e.stopPropagation()
resetProductQuantity(product.id)
}}
className="text-slate-400 hover:text-slate-600 p-1"
title="Reset quantity"
>
<RefreshCw className="w-4 h-4" />
</button>
</div>
{product.comment && <p className="text-sm text-slate-500 mb-2 line-clamp-2">{product.comment}</p>}
{product.imageUrl && (
<div className="w-full h-32 bg-gray-100 rounded-lg mb-3 overflow-hidden">
<img src={product.imageUrl} alt={product.name} className="w-full h-full object-cover" />
</div>
)}
</div>
<div className="px-4 pb-4">
<div className="flex items-center justify-center gap-0 mb-3">
<button
onClick={(e) => {
e.stopPropagation()
updateProductQuantity(product.id, -1)
}}
className="w-10 h-10 bg-slate-100 hover:bg-slate-200 flex items-center justify-center transition-colors border border-slate-300 rounded-l-md"
>
<Minus className="w-4 h-4 text-slate-600" />
</button>
<div className="w-12 h-10 bg-white border-y border-slate-300 flex items-center justify-center">
<span className="text-lg font-bold text-slate-800">{product.quantity}</span>
</div>
<button
onClick={(e) => {
e.stopPropagation()
updateProductQuantity(product.id, 1)
}}
className="w-10 h-10 bg-slate-100 hover:bg-slate-200 flex items-center justify-center transition-colors border border-slate-300 rounded-r-md"
>
<Plus className="w-4 h-4 text-slate-600" />
</button>
</div>
<button
onClick={(e) => {
e.stopPropagation()
toggleProductCompletion(product.id)
}}
className={`w-full px-3 py-2 text-sm border rounded-md transition-colors ${
product.isCompleted
? 'bg-green-100 border-green-300 text-green-700'
: 'border-slate-300 hover:bg-slate-50'
}`}
>
{product.isCompleted ? <Check className="w-3 h-3 inline mr-1" /> : null} {product.isCompleted ? 'Done' : 'Mark Done'}
</button>
</div>
</div>
))}
</div>
</div>
)}
{outOfStockProducts.length > 0 && (
<div className="mb-8">
<h2 className="text-xl font-semibold mb-4">Out of Stock ({outOfStockProducts.length})</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{outOfStockProducts.map(product => (
<div
key={product.id}
className="border-2 rounded-xl transition-all hover:shadow-md bg-red-50 border-red-200"
>
<div
className="p-4 cursor-pointer"
onClick={() => openEditModal(product)}
>
<div className="flex justify-between items-start mb-2">
<div className="flex items-center gap-2 flex-1">
<button
onClick={(e) => {
e.stopPropagation()
toggleOutOfStock(product.id)
}}
className="p-1 rounded-full hover:bg-gray-100 transition-colors"
title="Mark as in stock"
>
<ShoppingCart className="w-4 h-4 text-red-500" />
</button>
<h3 className={`font-semibold ${product.isCompleted ? 'text-green-600 line-through' : 'text-slate-800'}`}>
{product.name}
</h3>
</div>
<button
onClick={(e) => {
e.stopPropagation()
resetProductQuantity(product.id)
}}
className="text-slate-400 hover:text-slate-600 p-1"
title="Reset quantity"
>
<RefreshCw className="w-4 h-4" />
</button>
</div>
{product.comment && <p className="text-sm text-slate-500 mb-2 line-clamp-2">{product.comment}</p>}
{product.imageUrl && (
<div className="w-full h-32 bg-gray-100 rounded-lg mb-3 overflow-hidden">
<img src={product.imageUrl} alt={product.name} className="w-full h-full object-cover" />
</div>
)}
</div>
<div className="px-4 pb-4">
<div className="flex items-center justify-center gap-0 mb-3">
<button
onClick={(e) => {
e.stopPropagation()
updateProductQuantity(product.id, -1)
}}
className="w-10 h-10 bg-slate-100 hover:bg-slate-200 flex items-center justify-center transition-colors border border-slate-300 rounded-l-md"
>
<Minus className="w-4 h-4 text-slate-600" />
</button>
<div className="w-12 h-10 bg-white border-y border-slate-300 flex items-center justify-center">
<span className="text-lg font-bold text-slate-800">{product.quantity}</span>
</div>
<button
onClick={(e) => {
e.stopPropagation()
updateProductQuantity(product.id, 1)
}}
className="w-10 h-10 bg-slate-100 hover:bg-slate-200 flex items-center justify-center transition-colors border border-slate-300 rounded-r-md"
>
<Plus className="w-4 h-4 text-slate-600" />
</button>
</div>
<button
onClick={(e) => {
e.stopPropagation()
toggleProductCompletion(product.id)
}}
className={`w-full px-3 py-2 text-sm border rounded-md transition-colors ${
product.isCompleted
? 'bg-green-100 border-green-300 text-green-700'
: 'border-slate-300 hover:bg-slate-50'
}`}
>
{product.isCompleted ? <Check className="w-3 h-3 inline mr-1" /> : null} {product.isCompleted ? 'Done' : 'Mark Done'}
</button>
</div>
</div>
))}
</div>
</div>
)}
{selectedList.products.length === 0 && (
<div className="text-center py-12">
<div className="w-16 h-16 bg-slate-200 rounded-full flex items-center justify-center mx-auto mb-4">
<ShoppingCart className="w-8 h-8 text-slate-400" />
</div>
<p className="text-slate-500">No products in this list yet. Add some above!</p>
</div>
)}
</div>
</div>
)
} else {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 p-4">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-8">
<h1 className="text-4xl font-bold text-slate-800 mb-2">Gulu Inventory</h1>
<p className="text-slate-600 text-lg">Manage your shelf restocking efficiently</p>
</div>
<div className="flex flex-col sm:flex-row gap-4 mb-6">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400 w-4 h-4 pointer-events-none" />
<Input
placeholder="Search lists..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
/>
</div>
</div>
<div className="flex gap-2">
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value as 'name' | 'quantity' | 'date')}
className="px-3 py-2 border rounded-md bg-white"
>
<option value="date">Date</option>
<option value="name">Name</option>
<option value="quantity">Items</option>
</select>
<button
onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}
className="px-3 py-2 border rounded-md bg-white hover:bg-slate-50 transition-colors"
>
{sortOrder === 'asc' ? '↑' : '↓'}
</button>
<button
onClick={() => setShowNewListForm(true)}
style={{ backgroundColor: '#58ab7f', color: 'white' }}
className="px-4 py-2 rounded-md transition-colors flex items-center"
>
<Plus className="w-4 h-4 mr-2" />
New List
</button>
</div>
</div>
{showNewListForm && (
<Card className="mb-6">
<CardHeader>
<CardTitle>Create New List</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div>
<Label>List Name</Label>
<Input placeholder="Enter list name..." value={newListName} onChange={(e) => setNewListName(e.target.value)} />
</div>
<div>
<Label>Description</Label>
<Textarea placeholder="Enter description..." value={newListDescription} onChange={(e) => setNewListDescription(e.target.value)} />
</div>
<div className="flex gap-2">
<button
onClick={createNewList}
style={{ backgroundColor: '#58ab7f', color: 'white' }}
className="px-4 py-2 rounded-md transition-colors"
>
Create List
</button>
<button
onClick={() => {setShowNewListForm(false); setNewListName(''); setNewListDescription('')}}
className="px-4 py-2 border border-slate-300 rounded-md hover:bg-slate-50 transition-colors"
>
Cancel
</button>
</div>
</div>
</CardContent>
</Card>
)}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredLists.map(list => {
const completedCount = list.products.filter(p => p.isCompleted && !p.isOutOfStock).length
const totalCount = list.products.length
return (
<Card key={list.id} className="hover:shadow-lg transition-shadow">
<CardHeader className="pb-3">
<div className="flex justify-between items-start">
<div className="flex-1 pr-2">
<CardTitle className="text-lg">{list.name}</CardTitle>
<CardDescription>{list.description}</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="pb-3">
<div className="space-y-2">
<div className="flex justify-between"><span className="text-sm text-slate-600">Items</span><span className="text-sm font-medium">{totalCount}</span></div>
<div className="flex justify-between"><span className="text-sm text-slate-600">Completed</span><span className="text-sm font-medium text-green-600">{completedCount}</span></div>
<div className="flex justify-between"><span className="text-sm text-slate-600">Created</span><span className="text-sm text-slate-500">{list.createdAt.toLocaleDateString()}</span></div>
</div>
</CardContent>
<CardContent className="pt-0">
<div className="flex items-center gap-2">
<button
onClick={() => setSelectedListId(list.id)}
style={{ backgroundColor: '#58ab7f', color: 'white' }}
className="flex-1 px-4 py-2 rounded-md transition-colors"
>
Open List
</button>
<button
onClick={(e) => {
e.stopPropagation()
deleteList(list.id)
}}
style={{ color: '#ef4444' }}
className="p-2 rounded-md transition-colors"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</CardContent>
</Card>
)
})}
</div>
{filteredLists.length === 0 && (
<div className="text-center py-12">
<div className="w-16 h-16 bg-slate-200 rounded-full flex items-center justify-center mx-auto mb-4">
<ShoppingCart className="w-8 h-8 text-slate-400" />
</div>
<p className="text-slate-500">{searchQuery ? 'No lists found' : 'No lists created yet'}</p>
{!showNewListForm && (
<button
onClick={() => setShowNewListForm(true)}
style={{ backgroundColor: '#58ab7f', color: 'white' }}
className="px-4 py-2 rounded-md transition-colors mt-4"
>
Create your first list
</button>
)}
</div>
)}
</div>
</div>
)
}
}
return (
<>
{renderContent()}
{/* Edit Modal - Always rendered at the very root level */}
{showEditModal && editingProduct && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-2xl shadow-2xl max-w-md w-full p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold">Edit Product</h2>
<button
onClick={closeEditModal}
className="p-1 rounded-full hover:bg-gray-100 transition-colors"
>
<X className="w-5 h-5" />
</button>
</div>
<div className="space-y-4">
<div>
<Label>Product Name</Label>
<Input
value={editForm.name}
onChange={(e) => setEditForm({...editForm, name: e.target.value})}
placeholder="Enter product name"
/>
</div>
<div>
<Label>Image URL (optional)</Label>
<Input
value={editForm.imageUrl}
onChange={(e) => setEditForm({...editForm, imageUrl: e.target.value})}
placeholder="https://example.com/image.jpg"
/>
</div>
<div>
<Label>Comment (optional)</Label>
<Textarea
value={editForm.comment}
onChange={(e) => setEditForm({...editForm, comment: e.target.value})}
placeholder="Add a comment..."
rows={3}
/>
</div>
<div className="flex gap-2">
<button
onClick={saveProductEdit}
style={{ backgroundColor: '#58ab7f', color: 'white' }}
className="flex-1 px-4 py-2 rounded-md transition-colors"
>
Save Changes
</button>
<button
onClick={deleteProductFromEdit}
className="px-4 py-2 border border-red-300 text-red-600 rounded-md hover:bg-red-50 transition-colors"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
</div>
</div>
)}
</>
)
}