Update app
This commit is contained in:
parent
fbb3e7d850
commit
307bdef2e1
Binary file not shown.
Binary file not shown.
@ -13,6 +13,7 @@ class Model(Base):
|
||||
brand = Column(String(100), nullable=False)
|
||||
base_price = Column(DECIMAL(10, 2), nullable=True)
|
||||
sizes = Column(JSON, nullable=True)
|
||||
stock = Column(Integer, nullable=True)
|
||||
description = Column(String, nullable=True)
|
||||
created_at = Column(TIMESTAMP, default=datetime.utcnow)
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ class Product(Base):
|
||||
brand = Column(String)
|
||||
sizes = Column(JSON) # ["S", "M", "L", "XL", ...]
|
||||
colors = Column(JSON) # ["Red", "Blue", ...]
|
||||
stock = Column(Integer, default=0)
|
||||
stock = Column(Integer, nullable=True, default=None)
|
||||
images = Column(JSON) # Array of image URLs
|
||||
is_featured = Column(Boolean, default=False)
|
||||
is_on_sale = Column(Boolean, default=False)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -28,7 +28,8 @@ def create_category(
|
||||
db: Session = Depends(get_db),
|
||||
admin: User = Depends(get_current_admin_user)
|
||||
):
|
||||
db_category = Category(**category.dict())
|
||||
category_data = category.model_dump() if hasattr(category, 'model_dump') else category.dict()
|
||||
db_category = Category(**category_data)
|
||||
db.add(db_category)
|
||||
db.commit()
|
||||
db.refresh(db_category)
|
||||
@ -46,7 +47,8 @@ def update_category(
|
||||
if not category:
|
||||
raise HTTPException(status_code=404, detail="Category not found")
|
||||
|
||||
for field, value in category_update.dict(exclude_unset=True).items():
|
||||
update_data = category_update.model_dump(exclude_unset=True) if hasattr(category_update, 'model_dump') else category_update.dict(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(category, field, value)
|
||||
|
||||
db.commit()
|
||||
|
||||
@ -9,7 +9,8 @@ router = APIRouter(prefix="/api/contact", tags=["contact"])
|
||||
|
||||
@router.post("", response_model=ContactMessageResponse)
|
||||
def send_contact_message(message: ContactMessageCreate, db: Session = Depends(get_db)):
|
||||
db_message = ContactMessage(**message.dict())
|
||||
message_data = message.model_dump() if hasattr(message, 'model_dump') else message.dict()
|
||||
db_message = ContactMessage(**message_data)
|
||||
db.add(db_message)
|
||||
db.commit()
|
||||
db.refresh(db_message)
|
||||
|
||||
@ -55,7 +55,8 @@ def create_model(
|
||||
detail=f"Model '{model_data.name}' already exists for brand '{model_data.brand}' in this category"
|
||||
)
|
||||
|
||||
db_model = Model(**model_data.dict())
|
||||
model_dict = model_data.model_dump() if hasattr(model_data, 'model_dump') else model_data.dict()
|
||||
db_model = Model(**model_dict)
|
||||
db.add(db_model)
|
||||
db.commit()
|
||||
db.refresh(db_model)
|
||||
@ -74,7 +75,8 @@ def update_model(
|
||||
if not model:
|
||||
raise HTTPException(status_code=404, detail="Model not found")
|
||||
|
||||
for field, value in model_update.dict(exclude_unset=True).items():
|
||||
update_data = model_update.model_dump(exclude_unset=True) if hasattr(model_update, 'model_dump') else model_update.dict(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(model, field, value)
|
||||
|
||||
db.commit()
|
||||
|
||||
@ -21,14 +21,16 @@ def update_user_profile(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
for field, value in user_update.dict(exclude_unset=True).items():
|
||||
update_data = user_update.model_dump(exclude_unset=True) if hasattr(user_update, 'model_dump') else user_update.dict(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(current_user, field, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(current_user)
|
||||
return current_user
|
||||
|
||||
for field, value in user_update.dict(exclude_unset=True).items():
|
||||
update_data2 = user_update.model_dump(exclude_unset=True) if hasattr(user_update, 'model_dump') else user_update.dict(exclude_unset=True)
|
||||
for field, value in update_data2.items():
|
||||
setattr(user, field, value)
|
||||
|
||||
db.commit()
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -10,6 +10,7 @@ class ModelCreate(BaseModel):
|
||||
brand: str
|
||||
base_price: Optional[Decimal] = None
|
||||
sizes: Optional[List[str]] = []
|
||||
stock: Optional[int] = None
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
@ -19,6 +20,7 @@ class ModelUpdate(BaseModel):
|
||||
brand: Optional[str] = None
|
||||
base_price: Optional[Decimal] = None
|
||||
sizes: Optional[List[str]] = None
|
||||
stock: Optional[int] = None
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
@ -29,6 +31,7 @@ class ModelResponse(BaseModel):
|
||||
brand: str
|
||||
base_price: Optional[Decimal]
|
||||
sizes: Optional[List[str]]
|
||||
stock: Optional[int]
|
||||
description: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
|
||||
@ -14,10 +14,10 @@ class ProductCreate(BaseModel):
|
||||
model_id: Optional[int] = None
|
||||
gender: str # men, women
|
||||
brand: str
|
||||
sizes: List[str]
|
||||
sizes: Optional[List[str]] = []
|
||||
colors: Optional[List[str]] = []
|
||||
stock: int
|
||||
images: List[str]
|
||||
stock: Optional[int] = None
|
||||
images: Optional[List[str]] = []
|
||||
is_featured: bool = False
|
||||
is_on_sale: bool = False
|
||||
override_price: Optional[Decimal] = None
|
||||
@ -55,10 +55,10 @@ class ProductResponse(BaseModel):
|
||||
model_id: Optional[int]
|
||||
gender: str
|
||||
brand: str
|
||||
sizes: List[str]
|
||||
sizes: Optional[List[str]]
|
||||
colors: Optional[List[str]]
|
||||
stock: int
|
||||
images: List[str]
|
||||
stock: Optional[int]
|
||||
images: Optional[List[str]]
|
||||
is_featured: bool
|
||||
is_on_sale: bool
|
||||
override_price: Optional[Decimal]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -36,7 +36,8 @@ def add_to_cart(db: Session, user_id: int, item: CartItemCreate) -> CartItem:
|
||||
db.refresh(existing_item)
|
||||
return existing_item
|
||||
|
||||
cart_item = CartItem(cart_id=cart.id, **item.dict())
|
||||
item_data = item.model_dump() if hasattr(item, 'model_dump') else item.dict()
|
||||
cart_item = CartItem(cart_id=cart.id, **item_data)
|
||||
db.add(cart_item)
|
||||
db.commit()
|
||||
db.refresh(cart_item)
|
||||
|
||||
@ -29,12 +29,13 @@ def create_order(db: Session, user_id: int, order_data: OrderCreate) -> Optional
|
||||
|
||||
order_number = f"ORD-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex[:6].upper()}"
|
||||
|
||||
order_dict = order_data.model_dump() if hasattr(order_data, 'model_dump') else order_data.dict()
|
||||
order = Order(
|
||||
user_id=user_id,
|
||||
order_number=order_number,
|
||||
status="pending",
|
||||
total_amount=total_amount,
|
||||
**order_data.dict(),
|
||||
**order_dict,
|
||||
)
|
||||
|
||||
db.add(order)
|
||||
|
||||
@ -32,7 +32,8 @@ def get_product_by_id(db: Session, product_id: int) -> Optional[Product]:
|
||||
|
||||
|
||||
def create_product(db: Session, product: ProductCreate) -> Product:
|
||||
db_product = Product(**product.dict())
|
||||
product_data = product.model_dump() if hasattr(product, 'model_dump') else product.dict()
|
||||
db_product = Product(**product_data)
|
||||
db.add(db_product)
|
||||
db.commit()
|
||||
db.refresh(db_product)
|
||||
@ -44,7 +45,8 @@ def update_product(db: Session, product_id: int, product_update: ProductUpdate)
|
||||
if not db_product:
|
||||
return None
|
||||
|
||||
for field, value in product_update.dict(exclude_unset=True).items():
|
||||
update_data = product_update.model_dump(exclude_unset=True) if hasattr(product_update, 'model_dump') else product_update.dict(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(db_product, field, value)
|
||||
|
||||
db.commit()
|
||||
|
||||
2
backend/migrations/003_add_stock_to_model.sql
Normal file
2
backend/migrations/003_add_stock_to_model.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- Add stock column to model table
|
||||
ALTER TABLE model ADD COLUMN IF NOT EXISTS stock INTEGER DEFAULT NULL;
|
||||
3
backend/migrations/004_make_product_stock_nullable.sql
Normal file
3
backend/migrations/004_make_product_stock_nullable.sql
Normal file
@ -0,0 +1,3 @@
|
||||
-- Make product stock column nullable (allow hiding stock from customers)
|
||||
ALTER TABLE product ALTER COLUMN stock DROP DEFAULT;
|
||||
ALTER TABLE product ALTER COLUMN stock DROP NOT NULL;
|
||||
@ -28,13 +28,14 @@ export default function ProductCard({ product }) {
|
||||
<div className="price">
|
||||
{product.discount_price ? (
|
||||
<>
|
||||
<span className="original">${product.price.toFixed(2)}</span>
|
||||
<span className="discounted">${product.discount_price.toFixed(2)}</span>
|
||||
<span className="original">₪{product.price.toFixed(2)}</span>
|
||||
<span className="discounted">₪{product.discount_price.toFixed(2)}</span>
|
||||
</>
|
||||
) : (
|
||||
<span>${price.toFixed(2)}</span>
|
||||
<span>₪{price.toFixed(2)}</span>
|
||||
)}
|
||||
</div>
|
||||
{product.stock !== null && (
|
||||
<p className="stock">
|
||||
{product.stock > 0 ? (
|
||||
<span className="in-stock">In Stock</span>
|
||||
@ -42,6 +43,7 @@ export default function ProductCard({ product }) {
|
||||
<span className="out-of-stock">Out of Stock</span>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
<Link to={`/product/${product.id}`} className="btn btn-small">
|
||||
View Details
|
||||
</Link>
|
||||
|
||||
@ -40,7 +40,7 @@ export default function SearchBar() {
|
||||
{results.map((product) => (
|
||||
<a key={product.id} href={`/product/${product.id}`} className="search-result-item">
|
||||
<span>{product.name}</span>
|
||||
<span>${product.price.toFixed(2)}</span>
|
||||
<span>₪{product.price.toFixed(2)}</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -52,6 +52,17 @@ export default function Admin() {
|
||||
const [editingBrand, setEditingBrand] = useState(null)
|
||||
const [brandFormData, setBrandFormData] = useState({ name: '' })
|
||||
const [brandsList, setBrandsList] = useState([]) // Separate list for brand management
|
||||
const [showModelForm, setShowModelForm] = useState(false)
|
||||
const [editingModel, setEditingModel] = useState(null)
|
||||
const [modelFormData, setModelFormData] = useState({
|
||||
name: '',
|
||||
category_id: '',
|
||||
brand: '',
|
||||
base_price: '',
|
||||
sizes: '',
|
||||
stock: '',
|
||||
description: '',
|
||||
})
|
||||
|
||||
// Redirect if not admin
|
||||
useEffect(() => {
|
||||
@ -201,8 +212,8 @@ export default function Admin() {
|
||||
discount_price: formData.discount_price ? parseFloat(formData.discount_price) : null,
|
||||
category_id: parseInt(formData.category_id),
|
||||
model_id: formData.model_id ? parseInt(formData.model_id) : null,
|
||||
stock: parseInt(formData.stock),
|
||||
sizes: formData.sizes ? formData.sizes.split(',').map(s => s.trim()) : null,
|
||||
stock: formData.stock ? parseInt(formData.stock) : null,
|
||||
sizes: formData.sizes ? formData.sizes.split(',').map(s => s.trim()).filter(s => s) : [],
|
||||
images: formData.images.split(',').map(i => i.trim()).filter(i => i),
|
||||
override_price: formData.override_price ? parseFloat(formData.override_price) : null,
|
||||
override_sizes: formData.override_sizes ? formData.override_sizes.split(',').map(s => s.trim()) : null,
|
||||
@ -505,6 +516,94 @@ export default function Admin() {
|
||||
}
|
||||
}
|
||||
|
||||
// Model management functions
|
||||
const handleModelChange = (e) => {
|
||||
const { name, value } = e.target
|
||||
setModelFormData({ ...modelFormData, [name]: value })
|
||||
}
|
||||
|
||||
const handleModelSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
const modelData = {
|
||||
...modelFormData,
|
||||
category_id: parseInt(modelFormData.category_id),
|
||||
base_price: modelFormData.base_price ? parseFloat(modelFormData.base_price) : null,
|
||||
sizes: modelFormData.sizes ? modelFormData.sizes.split(',').map(s => s.trim()) : [],
|
||||
stock: modelFormData.stock ? parseInt(modelFormData.stock) : null,
|
||||
}
|
||||
|
||||
try {
|
||||
if (editingModel) {
|
||||
await api.put(`/models/${editingModel.id}`, modelData)
|
||||
alert('Model updated successfully!')
|
||||
} else {
|
||||
await api.post('/models', modelData)
|
||||
alert('Model created successfully!')
|
||||
}
|
||||
setShowModelForm(false)
|
||||
setEditingModel(null)
|
||||
resetModelForm()
|
||||
fetchModels()
|
||||
} catch (error) {
|
||||
console.error('Error saving model:', error)
|
||||
alert('Error saving model: ' + (error.response?.data?.detail || 'Unknown error'))
|
||||
}
|
||||
}
|
||||
|
||||
const handleModelEdit = (model) => {
|
||||
setEditingModel(model)
|
||||
setModelFormData({
|
||||
name: model.name || '',
|
||||
category_id: model.category_id || '',
|
||||
brand: model.brand || '',
|
||||
base_price: model.base_price || '',
|
||||
sizes: Array.isArray(model.sizes) ? model.sizes.join(', ') : '',
|
||||
stock: model.stock || '',
|
||||
description: model.description || '',
|
||||
})
|
||||
setShowModelForm(true)
|
||||
setTimeout(() => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const handleModelDelete = async (id) => {
|
||||
if (!confirm('Are you sure you want to delete this model? This will unlink all associated products.')) return
|
||||
|
||||
try {
|
||||
await api.delete(`/models/${id}`)
|
||||
alert('Model deleted successfully!')
|
||||
fetchModels()
|
||||
} catch (error) {
|
||||
console.error('Error deleting model:', error)
|
||||
alert('Error deleting model: ' + (error.response?.data?.detail || 'Unknown error'))
|
||||
}
|
||||
}
|
||||
|
||||
const resetModelForm = () => {
|
||||
setModelFormData({
|
||||
name: '',
|
||||
category_id: '',
|
||||
brand: '',
|
||||
base_price: '',
|
||||
sizes: '',
|
||||
stock: '',
|
||||
description: '',
|
||||
})
|
||||
}
|
||||
|
||||
const handleModelCancel = () => {
|
||||
setShowModelForm(false)
|
||||
setEditingModel(null)
|
||||
resetModelForm()
|
||||
}
|
||||
|
||||
const getCategoryName = (categoryId) => {
|
||||
const category = categories.find(c => c.id === categoryId)
|
||||
return category ? category.name : 'Unknown'
|
||||
}
|
||||
|
||||
if (!user?.is_admin) {
|
||||
return null
|
||||
}
|
||||
@ -560,18 +659,21 @@ export default function Admin() {
|
||||
>
|
||||
Brands
|
||||
</button>
|
||||
<Link
|
||||
to="/admin/models"
|
||||
<button
|
||||
onClick={() => setActiveTab('models')}
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
padding: '0.75rem 1.5rem',
|
||||
textDecoration: 'none',
|
||||
color: '#333',
|
||||
marginRight: '0.5rem',
|
||||
border: 'none',
|
||||
borderBottom: activeTab === 'models' ? '3px solid #007bff' : '3px solid transparent',
|
||||
backgroundColor: 'transparent',
|
||||
cursor: 'pointer',
|
||||
fontWeight: activeTab === 'models' ? 'bold' : 'normal',
|
||||
fontSize: '1rem'
|
||||
}}
|
||||
>
|
||||
Models
|
||||
</Link>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Products Section */}
|
||||
@ -771,8 +873,8 @@ export default function Admin() {
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Stock *</label>
|
||||
<input type="number" name="stock" value={formData.stock} onChange={handleChange} required />
|
||||
<label>Stock (Optional)</label>
|
||||
<input type="number" name="stock" value={formData.stock} onChange={handleChange} placeholder="Leave empty to hide from customers" />
|
||||
</div>
|
||||
|
||||
<div className="form-group" style={{ gridColumn: '1 / -1' }}>
|
||||
@ -936,8 +1038,8 @@ export default function Admin() {
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd', fontSize: '0.85em', color: '#666' }}>
|
||||
{product.slug || '-'}
|
||||
</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>${product.price}</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{product.stock}</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>₪{product.price}</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{product.stock ?? 'Hidden'}</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{product.is_featured ? '✓' : '-'}</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{product.is_on_sale ? '✓' : '-'}</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>
|
||||
@ -1219,6 +1321,199 @@ export default function Admin() {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Models Section */}
|
||||
{activeTab === 'models' && (
|
||||
<>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
|
||||
<h2>Manage Models</h2>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={() => {
|
||||
setShowModelForm(!showModelForm)
|
||||
setEditingModel(null)
|
||||
resetModelForm()
|
||||
}}
|
||||
>
|
||||
{showModelForm ? 'Cancel' : '+ Add New Model'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showModelForm && (
|
||||
<div style={{ backgroundColor: '#f5f5f5', padding: '2rem', borderRadius: '8px', marginBottom: '2rem' }}>
|
||||
<h2>{editingModel ? 'Edit Model' : 'Create New Model'}</h2>
|
||||
<form onSubmit={handleModelSubmit}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
|
||||
<div className="form-group">
|
||||
<label>Model Name * (e.g., 9060, Air Max 90)</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={modelFormData.name}
|
||||
onChange={handleModelChange}
|
||||
placeholder="9060"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Brand *</label>
|
||||
<select
|
||||
name="brand"
|
||||
value={modelFormData.brand}
|
||||
onChange={handleModelChange}
|
||||
required
|
||||
>
|
||||
<option value="">Select Brand</option>
|
||||
{brands.map(brand => (
|
||||
<option key={brand} value={brand}>{brand}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Category *</label>
|
||||
<select name="category_id" value={modelFormData.category_id} onChange={handleModelChange} required>
|
||||
<option value="">Select Category</option>
|
||||
{categories.map(category => (
|
||||
<option key={category.id} value={category.id}>
|
||||
{category.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Base Price (Default for all products)</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
name="base_price"
|
||||
value={modelFormData.base_price}
|
||||
onChange={handleModelChange}
|
||||
placeholder="129.99"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Stock (Optional)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="stock"
|
||||
value={modelFormData.stock}
|
||||
onChange={handleModelChange}
|
||||
placeholder="0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group" style={{ gridColumn: '1 / -1' }}>
|
||||
<label>Default Sizes (comma separated)</label>
|
||||
<input
|
||||
type="text"
|
||||
name="sizes"
|
||||
value={modelFormData.sizes}
|
||||
onChange={handleModelChange}
|
||||
placeholder="7, 7.5, 8, 8.5, 9, 9.5, 10, 10.5, 11, 11.5, 12"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group" style={{ gridColumn: '1 / -1' }}>
|
||||
<label>Description</label>
|
||||
<textarea
|
||||
name="description"
|
||||
value={modelFormData.description}
|
||||
onChange={handleModelChange}
|
||||
rows="4"
|
||||
placeholder="Model description..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '1.5rem' }}>
|
||||
<button type="submit" className="btn btn-primary" style={{ marginRight: '1rem' }}>
|
||||
{editingModel ? 'Update Model' : 'Create Model'}
|
||||
</button>
|
||||
<button type="button" className="btn btn-secondary" onClick={handleModelCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<h2>Models ({models.length})</h2>
|
||||
{loading ? (
|
||||
<p>Loading...</p>
|
||||
) : (
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', marginTop: '1rem' }}>
|
||||
<thead>
|
||||
<tr style={{ backgroundColor: '#f0f0f0', textAlign: 'left' }}>
|
||||
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>ID</th>
|
||||
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>Model Name</th>
|
||||
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>Brand</th>
|
||||
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>Category</th>
|
||||
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>Base Price</th>
|
||||
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>Stock</th>
|
||||
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>Sizes</th>
|
||||
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{models.map(model => (
|
||||
<tr key={model.id}>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{model.id}</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{model.name}</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{model.brand}</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{getCategoryName(model.category_id)}</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>
|
||||
{model.base_price ? `₪${parseFloat(model.base_price).toFixed(2)}` : '-'}
|
||||
</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>
|
||||
{model.stock ?? '-'}
|
||||
</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd', fontSize: '0.85em' }}>
|
||||
{model.sizes ? model.sizes.join(', ') : '-'}
|
||||
</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>
|
||||
<button
|
||||
onClick={() => handleModelEdit(model)}
|
||||
style={{
|
||||
marginRight: '0.5rem',
|
||||
padding: '0.25rem 0.75rem',
|
||||
backgroundColor: '#4CAF50',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleModelDelete(model.id)}
|
||||
style={{
|
||||
padding: '0.25rem 0.75rem',
|
||||
backgroundColor: '#f44336',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ export default function Cart() {
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>${(item.product.discount_price || item.product.price).toFixed(2)}</td>
|
||||
<td>₪{(item.product.discount_price || item.product.price).toFixed(2)}</td>
|
||||
<td>
|
||||
<div className="quantity-control">
|
||||
<button onClick={() => updateQuantity(index, item.quantity - 1)}>−</button>
|
||||
@ -64,7 +64,7 @@ export default function Cart() {
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
${((item.product.discount_price || item.product.price) * item.quantity).toFixed(2)}
|
||||
₪{((item.product.discount_price || item.product.price) * item.quantity).toFixed(2)}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
@ -85,7 +85,7 @@ export default function Cart() {
|
||||
<div className="summary-rows">
|
||||
<div className="summary-row">
|
||||
<span>Subtotal:</span>
|
||||
<span>${total.toFixed(2)}</span>
|
||||
<span>₪{total.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="summary-row">
|
||||
<span>Shipping:</span>
|
||||
@ -93,11 +93,11 @@ export default function Cart() {
|
||||
</div>
|
||||
<div className="summary-row">
|
||||
<span>Tax:</span>
|
||||
<span>${(total * 0.1).toFixed(2)}</span>
|
||||
<span>₪{(total * 0.1).toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="summary-row total">
|
||||
<span>Total:</span>
|
||||
<span>${(total + 10 + total * 0.1).toFixed(2)}</span>
|
||||
<span>₪{(total + 10 + total * 0.1).toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -119,12 +119,12 @@ export default function Checkout() {
|
||||
{cart.map((item, index) => (
|
||||
<div key={index} className="summary-item">
|
||||
<span>{item.product.name} x{item.quantity}</span>
|
||||
<span>${((item.product.discount_price || item.product.price) * item.quantity).toFixed(2)}</span>
|
||||
<span>₪{((item.product.discount_price || item.product.price) * item.quantity).toFixed(2)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="summary-total">
|
||||
<strong>Total: ${(total + 10 + total * 0.1).toFixed(2)}</strong>
|
||||
<strong>Total: ₪{(total + 10 + total * 0.1).toFixed(2)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -285,7 +285,7 @@ export default function Models() {
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{model.brand}</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{getCategoryName(model.category_id)}</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>
|
||||
{model.base_price ? `$${parseFloat(model.base_price).toFixed(2)}` : '-'}
|
||||
{model.base_price ? `₪${parseFloat(model.base_price).toFixed(2)}` : '-'}
|
||||
</td>
|
||||
<td style={{ padding: '0.75rem', border: '1px solid #ddd', fontSize: '0.85em' }}>
|
||||
{model.sizes ? model.sizes.join(', ') : '-'}
|
||||
|
||||
@ -58,7 +58,7 @@ export default function Orders() {
|
||||
</div>
|
||||
<div className="detail-row">
|
||||
<span>Total Amount:</span>
|
||||
<span>${order.total_amount.toFixed(2)}</span>
|
||||
<span>₪{order.total_amount.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="detail-row">
|
||||
<span>Items:</span>
|
||||
@ -74,7 +74,7 @@ export default function Orders() {
|
||||
<p>{item.product.name}</p>
|
||||
<p>Qty: {item.quantity}</p>
|
||||
</div>
|
||||
<p>${item.price.toFixed(2)}</p>
|
||||
<p>₪{item.price.toFixed(2)}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -141,11 +141,11 @@ export default function ProductDetail() {
|
||||
<div className="price">
|
||||
{product.discount_price ? (
|
||||
<>
|
||||
<span className="original">${product.price.toFixed(2)}</span>
|
||||
<span className="current">${product.discount_price.toFixed(2)}</span>
|
||||
<span className="original">₪{product.price.toFixed(2)}</span>
|
||||
<span className="current">₪{product.discount_price.toFixed(2)}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="current">${price.toFixed(2)}</span>
|
||||
<span className="current">₪{price.toFixed(2)}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -177,6 +177,7 @@ export default function ProductDetail() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{product.stock !== null && (
|
||||
<div className="stock-info">
|
||||
{product.stock > 0 ? (
|
||||
<span className="in-stock">✓ In Stock ({product.stock} available)</span>
|
||||
@ -184,6 +185,7 @@ export default function ProductDetail() {
|
||||
<span className="out-of-stock">✗ Out of Stock</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="action-buttons">
|
||||
<button
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user