dvirlabs 437fe72e48
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Manage contact us messages
2026-05-08 18:40:12 +03:00

1900 lines
73 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect, useContext } from 'react'
import { useNavigate, Link } from 'react-router-dom'
import api from '../api'
import { AuthContext } from '../context/AuthContext'
import Toast from '../components/Toast'
import '../styles/global.css'
export default function Admin() {
const navigate = useNavigate()
const { user, token } = useContext(AuthContext)
const [toast, setToast] = useState(null)
const [products, setProducts] = useState([])
const [categories, setCategories] = useState([])
const [loading, setLoading] = useState(true)
const [showForm, setShowForm] = useState(false)
const [editingProduct, setEditingProduct] = useState(null)
const [formData, setFormData] = useState({
name: '',
slug: '',
description: '',
price: '',
discount_price: '',
category_id: '',
model_id: '',
gender: 'men',
brand: '',
sizes: '',
stock: '',
images: '',
is_featured: false,
is_on_sale: false,
override_price: '',
override_sizes: '',
})
const [uploadingImage, setUploadingImage] = useState(false)
const [uploadedImages, setUploadedImages] = useState([])
const [models, setModels] = useState([])
const [activeTab, setActiveTab] = useState('products') // products, categories, brands, models, messages
const [showCategoryForm, setShowCategoryForm] = useState(false)
const [editingCategory, setEditingCategory] = useState(null)
const [categoryFormData, setCategoryFormData] = useState({
name: '',
slug: '',
description: '',
image: '',
})
const [searchQuery, setSearchQuery] = useState('')
const [filterBrand, setFilterBrand] = useState('')
const [filterCategory, setFilterCategory] = useState('')
const [filterModel, setFilterModel] = useState('')
const [brands, setBrands] = useState([])
const [allProducts, setAllProducts] = useState([]) // Store all products for filtering
const [showBrandForm, setShowBrandForm] = useState(false)
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: '',
})
// Contact Messages state
const [contactMessages, setContactMessages] = useState([])
const [filteredMessages, setFilteredMessages] = useState([])
const [unreadCount, setUnreadCount] = useState(0)
const [messageFilter, setMessageFilter] = useState('all') // all, new, read, replied
const [selectedMessage, setSelectedMessage] = useState(null)
const [showMessageModal, setShowMessageModal] = useState(false)
const [messageNotes, setMessageNotes] = useState('')
// Redirect if not admin
useEffect(() => {
if (!user?.is_admin) {
navigate('/')
}
}, [user, navigate])
useEffect(() => {
fetchProducts()
fetchCategories()
fetchModels()
fetchBrands()
fetchContactMessages()
fetchUnreadCount()
}, [])
const fetchProducts = async () => {
try {
const response = await api.get('/products')
setAllProducts(response.data)
setProducts(response.data)
} catch (error) {
console.error('Error fetching products:', error)
} finally {
setLoading(false)
}
}
const fetchCategories = async () => {
try {
const response = await api.get('/categories')
setCategories(response.data)
} catch (error) {
console.error('Error fetching categories:', error)
}
}
const fetchModels = async () => {
try {
const response = await api.get('/models')
setModels(response.data)
} catch (error) {
console.error('Error fetching models:', error)
}
}
const fetchBrands = async () => {
try {
const response = await api.get('/brands')
setBrands(response.data.map(b => b.name).sort())
setBrandsList(response.data)
} catch (error) {
console.error('Error fetching brands:', error)
}
}
const fetchContactMessages = async () => {
try {
const response = await api.get('/admin/contact-messages')
setContactMessages(response.data)
setFilteredMessages(response.data)
} catch (error) {
console.error('Error fetching contact messages:', error)
}
}
const fetchUnreadCount = async () => {
try {
const response = await api.get('/admin/contact-messages/unread-count')
setUnreadCount(response.data.unread_count)
} catch (error) {
console.error('Error fetching unread count:', error)
}
}
// Filter contact messages based on status
useEffect(() => {
if (messageFilter === 'all') {
setFilteredMessages(contactMessages)
} else {
setFilteredMessages(contactMessages.filter(m => m.status === messageFilter))
}
}, [messageFilter, contactMessages])
// Filter products based on search and filters
useEffect(() => {
let filtered = [...allProducts]
// Search filter
if (searchQuery) {
filtered = filtered.filter(p =>
p.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.brand?.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.description?.toLowerCase().includes(searchQuery.toLowerCase())
)
}
// Brand filter
if (filterBrand) {
filtered = filtered.filter(p => p.brand === filterBrand)
}
// Category filter
if (filterCategory) {
filtered = filtered.filter(p => p.category_id === parseInt(filterCategory))
}
// Model filter
if (filterModel) {
filtered = filtered.filter(p => p.model_id === parseInt(filterModel))
}
setProducts(filtered)
}, [searchQuery, filterBrand, filterCategory, filterModel, allProducts])
const handleChange = (e) => {
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value
setFormData({
...formData,
[e.target.name]: value,
})
}
const handleImageUpload = async (e) => {
const files = e.target.files
if (!files || files.length === 0) return
setUploadingImage(true)
const newImages = [...uploadedImages]
try {
for (let i = 0; i < files.length; i++) {
const file = files[i]
const formDataUpload = new FormData()
formDataUpload.append('file', file)
const response = await api.post('/products/upload-image', formDataUpload, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
// Backend now returns full URLs
newImages.push(response.data.url)
}
setUploadedImages(newImages)
// Update form data with new images
const currentImages = formData.images ? formData.images.split(',').map(s => s.trim()).filter(s => s) : []
const allImages = [...currentImages, ...newImages.map(url => url)]
setFormData({ ...formData, images: allImages.join(', ') })
setToast({ type: 'success', message: 'Images uploaded successfully!' })
} catch (error) {
console.error('Error uploading image:', error)
setToast({ type: 'error', message: 'Error uploading image: ' + (error.response?.data?.detail || 'Unknown error') })
} finally {
setUploadingImage(false)
}
}
const removeImage = (imageUrl) => {
const currentImages = formData.images.split(',').map(s => s.trim()).filter(s => s)
const filtered = currentImages.filter(img => img !== imageUrl)
setFormData({ ...formData, images: filtered.join(', ') })
setUploadedImages(uploadedImages.filter(img => img !== imageUrl))
}
const handleSubmit = async (e) => {
e.preventDefault()
const productData = {
...formData,
price: formData.price ? parseFloat(formData.price) : null, // Allow null - inherits from model
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: 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,
}
try {
if (editingProduct) {
await api.put(`/products/${editingProduct.id}`, productData)
setToast({ type: 'success', message: 'Product updated successfully!' })
} else {
await api.post('/products', productData)
setToast({ type: 'success', message: 'Product created successfully!' })
}
setShowForm(false)
setEditingProduct(null)
resetForm()
fetchProducts()
} catch (error) {
console.error('Error saving product:', error)
setToast({ type: 'error', message: 'Error saving product: ' + (error.response?.data?.detail || 'Unknown error') })
}
}
const handleEdit = (product) => {
console.log('Edit clicked for product:', product)
try {
setEditingProduct(product)
const imageList = Array.isArray(product.images) ? product.images : []
console.log('Setting form data...')
setFormData({
name: product.name || '',
slug: product.slug || '',
description: product.description || '',
price: product.price || '',
discount_price: product.discount_price || '',
category_id: product.category_id || '',
model_id: product.model_id || '',
gender: product.gender || 'men',
brand: product.brand || '',
sizes: Array.isArray(product.sizes) ? product.sizes.join(', ') : '',
stock: product.stock || '',
images: imageList.join(', '),
is_featured: product.is_featured || false,
is_on_sale: product.is_on_sale || false,
override_price: product.override_price || '',
override_sizes: Array.isArray(product.override_sizes) ? product.override_sizes.join(', ') : '',
})
setUploadedImages(imageList)
console.log('Showing form...')
setShowForm(true)
console.log('Form should now be visible')
// Scroll to top to show the form
setTimeout(() => {
window.scrollTo({ top: 0, behavior: 'smooth' })
}, 100)
} catch (error) {
console.error('Error in handleEdit:', error)
setToast({ type: 'error', message: 'Error loading product: ' + error.message })
}
}
const handleDelete = async (id) => {
if (!confirm('Are you sure you want to delete this product?')) return
try {
await api.delete(`/products/${id}`)
setToast({ type: 'success', message: 'Product deleted successfully!' })
fetchProducts()
} catch (error) {
console.error('Error deleting product:', error)
setToast({ type: 'error', message: 'Error deleting product' })
}
}
const resetForm = () => {
setFormData({
name: '',
slug: '',
description: '',
price: '',
discount_price: '',
category_id: '',
model_id: '',
gender: 'men',
brand: '',
sizes: '',
stock: '',
images: '',
is_featured: false,
is_on_sale: false,
override_price: '',
override_sizes: '',
})
setUploadedImages([])
}
const handleCancel = () => {
setShowForm(false)
setEditingProduct(null)
resetForm()
}
// Category Management Functions
const handleCategoryChange = (e) => {
const { name, value } = e.target
setCategoryFormData({ ...categoryFormData, [name]: value })
}
const handleCategoryImageUpload = async (e) => {
const file = e.target.files[0]
if (!file) return
setUploadingImage(true)
const formData = new FormData()
formData.append('file', file)
try {
const response = await api.post('/categories/upload-image', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
// Backend now returns full URLs
setCategoryFormData({ ...categoryFormData, image: response.data.url })
setToast({ type: 'success', message: 'Image uploaded successfully!' })
} catch (error) {
console.error('Error uploading image:', error)
setToast({ type: 'error', message: 'Error uploading image' })
} finally {
setUploadingImage(false)
}
}
const handleCategorySubmit = async (e) => {
e.preventDefault()
try {
if (editingCategory) {
await api.put(`/categories/${editingCategory.id}`, categoryFormData)
setToast({ type: 'success', message: 'Category updated successfully!' })
} else {
await api.post('/categories', categoryFormData)
setToast({ type: 'success', message: 'Category created successfully!' })
}
setShowCategoryForm(false)
setEditingCategory(null)
resetCategoryForm()
fetchCategories()
} catch (error) {
console.error('Error saving category:', error)
setToast({ type: 'error', message: 'Error saving category: ' + (error.response?.data?.detail || 'Unknown error') })
}
}
const handleEditCategory = (category) => {
setEditingCategory(category)
setCategoryFormData({
name: category.name || '',
slug: category.slug || '',
description: category.description || '',
image: category.image || '',
})
setShowCategoryForm(true)
setTimeout(() => {
window.scrollTo({ top: 0, behavior: 'smooth' })
}, 100)
}
const handleDeleteCategory = async (id) => {
if (!confirm('Are you sure you want to delete this category?')) return
try {
await api.delete(`/categories/${id}`)
setToast({ type: 'success', message: 'Category deleted successfully!' })
fetchCategories()
} catch (error) {
console.error('Error deleting category:', error)
setToast({ type: 'error', message: 'Error deleting category: ' + (error.response?.data?.detail || 'Unknown error') })
}
}
const resetCategoryForm = () => {
setCategoryFormData({
name: '',
slug: '',
description: '',
image: '',
})
}
// Brand management handlers
const handleAddBrand = async () => {
const newBrand = brandFormData.name.trim()
if (!newBrand) {
setToast({ type: 'error', message: 'Please enter a brand name' })
return
}
try {
await api.post('/brands', { name: newBrand })
setShowBrandForm(false)
setBrandFormData({ name: '' })
fetchBrands()
setToast({ type: 'success', message: 'Brand added successfully!' })
} catch (error) {
console.error('Error adding brand:', error)
setToast({ type: 'error', message: 'Error adding brand: ' + (error.response?.data?.detail || 'Unknown error') })
}
}
const handleUpdateBrand = async () => {
const oldBrand = editingBrand
const newBrand = brandFormData.name.trim()
if (!newBrand) {
setToast({ type: 'error', message: 'Please enter a brand name' })
return
}
if (oldBrand === newBrand) {
setShowBrandForm(false)
setEditingBrand(null)
return
}
if (!confirm(`Update brand from "${oldBrand}" to "${newBrand}"?`)) {
return
}
try {
// Find the brand ID
const brandToUpdate = brandsList.find(b => b.name === oldBrand)
if (!brandToUpdate) {
setToast({ type: 'error', message: 'Brand not found' })
return
}
// Update the brand
await api.put(`/brands/${brandToUpdate.id}`, { name: newBrand })
// Update all products with this brand
const productsToUpdate = allProducts.filter(p => p.brand === oldBrand)
for (const product of productsToUpdate) {
await api.put(`/products/${product.id}`, { ...product, brand: newBrand })
}
// Update all models with this brand
const modelsToUpdate = models.filter(m => m.brand === oldBrand)
for (const model of modelsToUpdate) {
await api.put(`/models/${model.id}`, { ...model, brand: newBrand })
}
setShowBrandForm(false)
setEditingBrand(null)
setBrandFormData({ name: '' })
// Refresh data
fetchBrands()
fetchProducts()
fetchModels()
setToast({ type: 'success', message: 'Brand updated successfully!' })
} catch (error) {
console.error('Error updating brand:', error)
setToast({ type: 'error', message: 'Error updating brand: ' + (error.response?.data?.detail || 'Unknown error') })
}
}
const handleDeleteBrand = async (brand) => {
const productCount = allProducts.filter(p => p.brand === brand).length
const modelCount = models.filter(m => m.brand === brand).length
if (productCount > 0 || modelCount > 0) {
setToast({ type: 'error', message: `Cannot delete "${brand}". It is used in ${productCount} product(s) and ${modelCount} model(s).` })
return
}
if (!confirm(`Are you sure you want to delete the brand "${brand}"?`)) {
return
}
try {
// Find the brand ID
const brandToDelete = brandsList.find(b => b.name === brand)
if (!brandToDelete) {
setToast({ type: 'error', message: 'Brand not found' })
return
}
await api.delete(`/brands/${brandToDelete.id}`)
fetchBrands()
setToast({ type: 'success', message: 'Brand deleted successfully!' })
} catch (error) {
console.error('Error deleting brand:', error)
setToast({ type: 'error', message: 'Error deleting brand: ' + (error.response?.data?.detail || 'Unknown error') })
}
}
// 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)
setToast({ type: 'success', message: 'Model updated successfully!' })
} else {
await api.post('/models', modelData)
setToast({ type: 'success', message: 'Model created successfully!' })
}
setShowModelForm(false)
setEditingModel(null)
resetModelForm()
fetchModels()
} catch (error) {
console.error('Error saving model:', error)
setToast({ type: 'error', message: '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}`)
setToast({ type: 'success', message: 'Model deleted successfully!' })
fetchModels()
} catch (error) {
console.error('Error deleting model:', error)
setToast({ type: 'error', message: '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()
}
// Contact Message Handlers
const handleUpdateMessage = async (messageId) => {
try {
await api.put(`/admin/contact-messages/${messageId}`, {
status: selectedMessage.status,
admin_notes: messageNotes,
is_read: true
})
setToast({ type: 'success', message: 'Message updated successfully!' })
setShowMessageModal(false)
fetchContactMessages()
fetchUnreadCount()
} catch (error) {
console.error('Error updating message:', error)
setToast({ type: 'error', message: 'Error updating message: ' + (error.response?.data?.detail || 'Unknown error') })
}
}
const handleDeleteMessage = async (messageId) => {
if (!confirm('Are you sure you want to delete this message?')) return
try {
await api.delete(`/admin/contact-messages/${messageId}`)
setToast({ type: 'success', message: 'Message deleted successfully!' })
fetchContactMessages()
fetchUnreadCount()
if (showMessageModal) {
setShowMessageModal(false)
}
} catch (error) {
console.error('Error deleting message:', error)
setToast({ type: 'error', message: 'Error deleting message: ' + (error.response?.data?.detail || 'Unknown error') })
}
}
const getCategoryName = (categoryId) => {
const category = categories.find(c => c.id === categoryId)
return category ? category.name : 'Unknown'
}
if (!user?.is_admin) {
return null
}
return (
<div className="admin-page" style={{ padding: '2rem', maxWidth: '1200px', margin: '0 auto' }}>
<h1 style={{ marginBottom: '1.5rem' }}>Admin Dashboard</h1>
{/* Tab Navigation */}
<div style={{ marginBottom: '2rem', borderBottom: '2px solid #ddd' }}>
<button
onClick={() => setActiveTab('products')}
style={{
padding: '0.75rem 1.5rem',
marginRight: '0.5rem',
border: 'none',
borderBottom: activeTab === 'products' ? '3px solid #007bff' : '3px solid transparent',
backgroundColor: 'transparent',
cursor: 'pointer',
fontWeight: activeTab === 'products' ? 'bold' : 'normal',
fontSize: '1rem'
}}
>
Products
</button>
<button
onClick={() => setActiveTab('categories')}
style={{
padding: '0.75rem 1.5rem',
marginRight: '0.5rem',
border: 'none',
borderBottom: activeTab === 'categories' ? '3px solid #007bff' : '3px solid transparent',
backgroundColor: 'transparent',
cursor: 'pointer',
fontWeight: activeTab === 'categories' ? 'bold' : 'normal',
fontSize: '1rem'
}}
>
Categories
</button>
<button
onClick={() => setActiveTab('brands')}
style={{
padding: '0.75rem 1.5rem',
marginRight: '0.5rem',
border: 'none',
borderBottom: activeTab === 'brands' ? '3px solid #007bff' : '3px solid transparent',
backgroundColor: 'transparent',
cursor: 'pointer',
fontWeight: activeTab === 'brands' ? 'bold' : 'normal',
fontSize: '1rem'
}}
>
Brands
</button>
<button
onClick={() => setActiveTab('models')}
style={{
padding: '0.75rem 1.5rem',
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
</button>
<button
onClick={() => setActiveTab('messages')}
style={{
padding: '0.75rem 1.5rem',
marginRight: '0.5rem',
border: 'none',
borderBottom: activeTab === 'messages' ? '3px solid #007bff' : '3px solid transparent',
backgroundColor: 'transparent',
cursor: 'pointer',
fontWeight: activeTab === 'messages' ? 'bold' : 'normal',
fontSize: '1rem',
position: 'relative'
}}
>
Contact Messages
{unreadCount > 0 && (
<span style={{
position: 'absolute',
top: '5px',
right: '5px',
backgroundColor: '#dc3545',
color: 'white',
borderRadius: '50%',
padding: '2px 6px',
fontSize: '0.7rem',
fontWeight: 'bold'
}}>
{unreadCount}
</span>
)}
</button>
</div>
{/* Products Section */}
{activeTab === 'products' && (
<>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
<h2>Manage Products</h2>
<button
className="btn btn-primary"
onClick={() => {
setShowForm(!showForm)
setEditingProduct(null)
resetForm()
}}
>
{showForm ? 'Cancel' : '+ Add New Product'}
</button>
</div>
{/* Search and Filters */}
<div style={{
backgroundColor: '#f8f9fa',
padding: '1.5rem',
borderRadius: '8px',
marginBottom: '2rem',
display: 'grid',
gridTemplateColumns: '2fr 1fr 1fr 1fr',
gap: '1rem',
alignItems: 'end'
}}>
<div>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: '500' }}>Search</label>
<input
type="text"
placeholder="Search by name, brand, description..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
style={{
width: '100%',
padding: '0.5rem',
border: '1px solid #ddd',
borderRadius: '4px'
}}
/>
</div>
<div>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: '500' }}>Brand</label>
<select
value={filterBrand}
onChange={(e) => setFilterBrand(e.target.value)}
style={{
width: '100%',
padding: '0.5rem',
border: '1px solid #ddd',
borderRadius: '4px'
}}
>
<option value="">All Brands</option>
{brands.map(brand => (
<option key={brand} value={brand}>{brand}</option>
))}
</select>
</div>
<div>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: '500' }}>Category</label>
<select
value={filterCategory}
onChange={(e) => setFilterCategory(e.target.value)}
style={{
width: '100%',
padding: '0.5rem',
border: '1px solid #ddd',
borderRadius: '4px'
}}
>
<option value="">All Categories</option>
{categories.map(cat => (
<option key={cat.id} value={cat.id}>{cat.name}</option>
))}
</select>
</div>
<div>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: '500' }}>Model</label>
<select
value={filterModel}
onChange={(e) => setFilterModel(e.target.value)}
style={{
width: '100%',
padding: '0.5rem',
border: '1px solid #ddd',
borderRadius: '4px'
}}
>
<option value="">All Models</option>
{models.map(model => (
<option key={model.id} value={model.id}>
{model.brand} {model.name}
</option>
))}
</select>
</div>
</div>
{showForm && (
<div style={{ backgroundColor: '#f5f5f5', padding: '2rem', borderRadius: '8px', marginBottom: '2rem' }}>
<h2>{editingProduct ? 'Edit Product' : 'Create New Product'}</h2>
<form onSubmit={handleSubmit}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
<div className="form-group">
<label>Name *</label>
<input type="text" name="name" value={formData.name} onChange={handleChange} required />
</div>
<div className="form-group">
<label>Brand</label>
<select name="brand" value={formData.brand} onChange={handleChange}>
<option value="">Select Brand</option>
{brands.map(brand => (
<option key={brand} value={brand}>{brand}</option>
))}
</select>
</div>
<div className="form-group" style={{ gridColumn: '1 / -1' }}>
<label>SEO-Friendly URL (Slug)</label>
<input
type="text"
name="slug"
value={formData.slug}
onChange={handleChange}
placeholder="Leave empty to auto-generate from brand and name"
style={{ backgroundColor: '#f9f9f9' }}
/>
{(formData.brand || formData.name) && !formData.slug && (
<small style={{ color: '#666', marginTop: '0.25rem', display: 'block' }}>
Will generate: {(formData.brand + ' ' + formData.name).toLowerCase().replace(/[^\w\s-]/g, '').replace(/[\s_-]+/g, '-')}
</small>
)}
</div>
<div className="form-group" style={{ gridColumn: '1 / -1' }}>
<label>Description</label>
<textarea name="description" value={formData.description} onChange={handleChange} rows="3" />
</div>
<div className="form-group">
<label>
Price {formData.model_id && models.find(m => m.id === parseInt(formData.model_id))?.base_price ? '(Optional - inherits from model)' : '*'}
</label>
<input
type="number"
step="0.01"
name="price"
value={formData.price}
onChange={handleChange}
required={!formData.model_id || !models.find(m => m.id === parseInt(formData.model_id))?.base_price}
placeholder={
formData.model_id && models.find(m => m.id === parseInt(formData.model_id))?.base_price
? `Model price: ₪${parseFloat(models.find(m => m.id === parseInt(formData.model_id)).base_price).toFixed(2)}`
: 'Enter price'
}
/>
{formData.model_id && models.find(m => m.id === parseInt(formData.model_id))?.base_price && (
<small style={{ color: '#666', fontSize: '0.85em' }}>
Leave empty to use model's base price of ₪{parseFloat(models.find(m => m.id === parseInt(formData.model_id)).base_price).toFixed(2)}
</small>
)}
</div>
<div className="form-group">
<label>Discount Price</label>
<input type="number" step="0.01" name="discount_price" value={formData.discount_price} onChange={handleChange} />
</div>
<div className="form-group">
<label>Category *</label>
<select name="category_id" value={formData.category_id} onChange={handleChange} required>
<option value="">Select category</option>
{categories.map(cat => (
<option key={cat.id} value={cat.id}>{cat.name}</option>
))}
</select>
</div>
<div className="form-group">
<label>Model (Optional - for shared attributes)</label>
<select name="model_id" value={formData.model_id} onChange={handleChange}>
<option value="">None - Standalone Product</option>
{models
.filter(m => {
const categoryMatch = !formData.category_id || m.category_id === parseInt(formData.category_id)
const brandMatch = !formData.brand || m.brand === formData.brand
return categoryMatch && brandMatch
})
.map(model => (
<option key={model.id} value={model.id}>
{model.brand} {model.name}
</option>
))}
</select>
<small style={{ color: '#666', fontSize: '0.85em' }}>
{formData.brand ? `Showing models for ${formData.brand}` : 'Select a brand to see related models'}
</small>
</div>
<div className="form-group">
<label>Gender</label>
<select name="gender" value={formData.gender} onChange={handleChange}>
<option value="men">Men</option>
<option value="women">Women</option>
<option value="unisex">Unisex</option>
</select>
</div>
<div className="form-group">
<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' }}>
<label>
Override Price (Leave empty to use model default)
{formData.model_id && <span style={{ color: '#007bff', marginLeft: '0.5rem' }}>Using Model Default</span>}
</label>
<input
type="number"
step="0.01"
name="override_price"
value={formData.override_price}
onChange={handleChange}
placeholder={formData.model_id ? "Model default will be used" : "Override price for this product"}
/>
</div>
<div className="form-group" style={{ gridColumn: '1 / -1' }}>
<label>
Sizes (comma separated)
{formData.model_id ? ' - Override model default' : ' *'}
</label>
<input
type="text"
name="sizes"
value={formData.sizes}
onChange={handleChange}
placeholder={formData.model_id ? "Leave empty to use model sizes" : "S, M, L, XL"}
required={!formData.model_id}
/>
</div>
<div className="form-group" style={{ gridColumn: '1 / -1' }}>
<label>Product Images</label>
{/* Image Upload - Multiple Files */}
<div style={{ marginBottom: '1rem' }}>
<input
type="file"
accept="image/*"
multiple
onChange={handleImageUpload}
disabled={uploadingImage}
style={{ display: 'block', marginBottom: '0.5rem' }}
/>
<small style={{ color: '#666', fontSize: '0.85em' }}>
Tip: Select multiple images or upload a folder
</small>
{uploadingImage && <p style={{ color: '#666' }}>Uploading images...</p>}
</div>
{/* Image Preview */}
{formData.images && (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))', gap: '1rem' }}>
{formData.images.split(',').map((img, idx) => {
const imgUrl = img.trim()
if (!imgUrl) return null
return (
<div key={idx} style={{ position: 'relative', border: '1px solid #ddd', borderRadius: '4px', padding: '0.5rem' }}>
<img
src={imgUrl}
alt={`Product ${idx + 1}`}
style={{ width: '100%', height: '100px', objectFit: 'cover', borderRadius: '4px' }}
onError={(e) => {
e.target.src = 'https://via.placeholder.com/100?text=Invalid'
}}
/>
<button
type="button"
onClick={() => removeImage(imgUrl)}
style={{
position: 'absolute',
top: '0.25rem',
right: '0.25rem',
background: 'red',
color: 'white',
border: 'none',
borderRadius: '50%',
width: '24px',
height: '24px',
cursor: 'pointer',
fontSize: '14px',
lineHeight: '1'
}}
>
×
</button>
</div>
)
})}
</div>
)}
{/* Manual URL input (optional) */}
<details style={{ marginTop: '1rem' }}>
<summary style={{ cursor: 'pointer', color: '#666' }}>Or add image URLs manually</summary>
<input
type="text"
name="images"
value={formData.images}
onChange={handleChange}
placeholder="https://example.com/image1.jpg, https://..."
style={{ marginTop: '0.5rem', width: '100%' }}
/>
</details>
</div>
<div className="form-group">
<label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<input type="checkbox" name="is_featured" checked={formData.is_featured} onChange={handleChange} />
Featured Product
</label>
</div>
<div className="form-group">
<label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<input type="checkbox" name="is_on_sale" checked={formData.is_on_sale} onChange={handleChange} />
On Sale
</label>
</div>
</div>
<div style={{ marginTop: '1.5rem', display: 'flex', gap: '1rem' }}>
<button type="submit" className="btn btn-primary">
{editingProduct ? 'Update Product' : 'Create Product'}
</button>
<button type="button" className="btn" onClick={handleCancel} style={{ background: '#666' }}>
Cancel
</button>
</div>
</form>
</div>
)}
<div>
<h2>Products ({products.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' }}>Name</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>Brand</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>Slug</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>Price</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>Stock</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>Featured</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>On Sale</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd' }}>Actions</th>
</tr>
</thead>
<tbody>
{products.map(product => (
<tr key={product.id}>
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{product.id}</td>
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{product.name}</td>
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{product.brand}</td>
<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 ?? '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' }}>
<button
onClick={() => handleEdit(product)}
style={{ marginRight: '0.5rem', padding: '0.25rem 0.75rem', backgroundColor: '#4CAF50', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }}
>
Edit
</button>
<button
onClick={() => handleDelete(product.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>
</>
)}
{/* Categories Section */}
{activeTab === 'categories' && (
<>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
<h2>Manage Categories</h2>
<button
className="btn btn-primary"
onClick={() => {
setShowCategoryForm(!showCategoryForm)
setEditingCategory(null)
resetCategoryForm()
}}
>
{showCategoryForm ? 'Cancel' : '+ Add New Category'}
</button>
</div>
{showCategoryForm && (
<div style={{ backgroundColor: '#f5f5f5', padding: '2rem', borderRadius: '8px', marginBottom: '2rem' }}>
<h2>{editingCategory ? 'Edit Category' : 'Create New Category'}</h2>
<form onSubmit={handleCategorySubmit}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
<div className="form-group">
<label>Name *</label>
<input
type="text"
name="name"
value={categoryFormData.name}
onChange={handleCategoryChange}
required
/>
</div>
<div className="form-group">
<label>Slug *</label>
<input
type="text"
name="slug"
value={categoryFormData.slug}
onChange={handleCategoryChange}
placeholder="e.g., shoes, shirts, pants"
required
/>
</div>
<div className="form-group" style={{ gridColumn: '1 / -1' }}>
<label>Description</label>
<textarea
name="description"
value={categoryFormData.description}
onChange={handleCategoryChange}
rows="3"
placeholder="Category description"
/>
</div>
<div className="form-group" style={{ gridColumn: '1 / -1' }}>
<label>Category Image</label>
<input
type="file"
accept="image/*"
onChange={handleCategoryImageUpload}
disabled={uploadingImage}
style={{ display: 'block', marginBottom: '0.5rem' }}
/>
{uploadingImage && <p style={{ color: '#666' }}>Uploading image...</p>}
{categoryFormData.image && (
<div style={{ marginTop: '1rem' }}>
<img
src={categoryFormData.image}
alt="Category preview"
style={{ maxWidth: '200px', maxHeight: '200px', objectFit: 'cover', borderRadius: '8px' }}
/>
</div>
)}
</div>
</div>
<div style={{ marginTop: '1.5rem' }}>
<button type="submit" className="btn btn-primary" style={{ marginRight: '1rem' }}>
{editingCategory ? 'Update Category' : 'Create Category'}
</button>
<button
type="button"
className="btn btn-secondary"
onClick={() => {
setShowCategoryForm(false)
setEditingCategory(null)
resetCategoryForm()
}}
>
Cancel
</button>
</div>
</form>
</div>
)}
<div>
<h3>Categories ({categories.length})</h3>
{loading ? (
<p>Loading...</p>
) : (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '1.5rem', marginTop: '1rem' }}>
{categories.map(category => (
<div key={category.id} style={{ border: '1px solid #ddd', borderRadius: '8px', padding: '1rem', backgroundColor: 'white' }}>
{category.image && (
<img
src={category.image}
alt={category.name}
style={{ width: '100%', height: '150px', objectFit: 'cover', borderRadius: '4px', marginBottom: '1rem' }}
/>
)}
<h4 style={{ margin: '0.5rem 0' }}>{category.name}</h4>
<p style={{ fontSize: '0.85em', color: '#666', margin: '0.25rem 0' }}>Slug: {category.slug}</p>
{category.description && (
<p style={{ fontSize: '0.9em', color: '#555', margin: '0.5rem 0' }}>{category.description}</p>
)}
<div style={{ marginTop: '1rem', display: 'flex', gap: '0.5rem' }}>
<button
onClick={() => handleEditCategory(category)}
style={{ flex: 1, padding: '0.5rem', backgroundColor: '#4CAF50', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }}
>
Edit
</button>
<button
onClick={() => handleDeleteCategory(category.id)}
style={{ flex: 1, padding: '0.5rem', backgroundColor: '#f44336', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }}
>
Delete
</button>
</div>
</div>
))}
</div>
)}
</div>
</>
)}
{/* Brands Section */}
{activeTab === 'brands' && (
<>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
<h2>Manage Brands</h2>
<button
className="btn btn-primary"
onClick={() => {
setShowBrandForm(!showBrandForm)
setEditingBrand(null)
setBrandFormData({ name: '' })
}}
>
{showBrandForm ? 'Cancel' : '+ Add New Brand'}
</button>
</div>
{showBrandForm && (
<div style={{ backgroundColor: '#f5f5f5', padding: '2rem', borderRadius: '8px', marginBottom: '2rem' }}>
<h2>{editingBrand ? 'Edit Brand' : 'Add New Brand'}</h2>
<form onSubmit={(e) => {
e.preventDefault()
if (editingBrand) {
handleUpdateBrand()
} else {
handleAddBrand()
}
}}>
<div className="form-group">
<label>Brand Name *</label>
<input
type="text"
name="name"
value={brandFormData.name}
onChange={(e) => setBrandFormData({ name: e.target.value })}
placeholder="e.g., Nike, Adidas, New Balance"
required
style={{ width: '100%', maxWidth: '400px' }}
/>
</div>
<div style={{ marginTop: '1.5rem' }}>
<button type="submit" className="btn btn-primary" style={{ marginRight: '1rem' }}>
{editingBrand ? 'Update Brand' : 'Add Brand'}
</button>
<button
type="button"
className="btn btn-secondary"
onClick={() => {
setShowBrandForm(false)
setEditingBrand(null)
setBrandFormData({ name: '' })
}}
>
Cancel
</button>
</div>
</form>
</div>
)}
<div>
<h3>Brands ({brands.length})</h3>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '1rem', marginTop: '1rem' }}>
{brands.map((brand, index) => {
const productCount = allProducts.filter(p => p.brand === brand).length
const modelCount = models.filter(m => m.brand === brand).length
return (
<div key={index} style={{
border: '1px solid #ddd',
borderRadius: '8px',
padding: '1rem',
backgroundColor: 'white',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between'
}}>
<div>
<h4 style={{ margin: '0 0 0.5rem 0' }}>{brand}</h4>
<p style={{ fontSize: '0.85em', color: '#666', margin: '0.25rem 0' }}>
{productCount} product{productCount !== 1 ? 's' : ''}
</p>
<p style={{ fontSize: '0.85em', color: '#666', margin: '0.25rem 0' }}>
{modelCount} model{modelCount !== 1 ? 's' : ''}
</p>
</div>
<div style={{ marginTop: '1rem', display: 'flex', gap: '0.5rem' }}>
<button
onClick={() => {
setEditingBrand(brand)
setBrandFormData({ name: brand })
setShowBrandForm(true)
setTimeout(() => window.scrollTo({ top: 0, behavior: 'smooth' }), 100)
}}
style={{ flex: 1, padding: '0.5rem', backgroundColor: '#4CAF50', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }}
>
Edit
</button>
<button
onClick={() => handleDeleteBrand(brand)}
style={{ flex: 1, padding: '0.5rem', backgroundColor: '#f44336', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }}
disabled={productCount > 0 || modelCount > 0}
title={productCount > 0 || modelCount > 0 ? 'Cannot delete brand in use' : 'Delete brand'}
>
Delete
</button>
</div>
</div>
)
})}
</div>
</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>
</>
)}
{/* Contact Messages Section */}
{activeTab === 'messages' && (
<>
<div style={{ marginBottom: '2rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
<h2>Contact Messages</h2>
<div style={{ display: 'flex', gap: '1rem' }}>
<select
value={messageFilter}
onChange={(e) => setMessageFilter(e.target.value)}
style={{
padding: '0.5rem 1rem',
borderRadius: '4px',
border: '1px solid #ddd'
}}
>
<option value="all">All Messages</option>
<option value="new">New</option>
<option value="read">Read</option>
<option value="replied">Replied</option>
</select>
</div>
</div>
{filteredMessages.length === 0 ? (
<p style={{ textAlign: 'center', color: '#666' }}>No messages found.</p>
) : (
<div style={{ overflowX: 'auto' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', backgroundColor: 'white', boxShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>
<thead>
<tr style={{ backgroundColor: '#f8f9fa' }}>
<th style={{ padding: '0.75rem', border: '1px solid #ddd', textAlign: 'left' }}>ID</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd', textAlign: 'left' }}>Name</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd', textAlign: 'left' }}>Email</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd', textAlign: 'left' }}>Phone</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd', textAlign: 'left' }}>Subject</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd', textAlign: 'left' }}>Date</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd', textAlign: 'left' }}>Status</th>
<th style={{ padding: '0.75rem', border: '1px solid #ddd', textAlign: 'left' }}>Actions</th>
</tr>
</thead>
<tbody>
{filteredMessages.map(msg => (
<tr
key={msg.id}
style={{
backgroundColor: !msg.is_read ? '#fff3cd' : 'white',
cursor: 'pointer'
}}
onClick={() => {
setSelectedMessage(msg)
setMessageNotes(msg.admin_notes || '')
setShowMessageModal(true)
}}
>
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>
{!msg.is_read && <span style={{ color: '#dc3545', marginRight: '5px', fontWeight: 'bold' }}></span>}
{msg.id}
</td>
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{msg.full_name}</td>
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{msg.email}</td>
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{msg.phone || '-'}</td>
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>{msg.subject}</td>
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>
{new Date(msg.created_at).toLocaleDateString()}
</td>
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>
<span style={{
padding: '0.25rem 0.5rem',
borderRadius: '12px',
fontSize: '0.85rem',
fontWeight: 'bold',
backgroundColor: msg.status === 'new' ? '#dc3545' : msg.status === 'read' ? '#ffc107' : '#28a745',
color: 'white'
}}>
{msg.status.toUpperCase()}
</span>
</td>
<td style={{ padding: '0.75rem', border: '1px solid #ddd' }}>
<button
onClick={(e) => {
e.stopPropagation()
handleDeleteMessage(msg.id)
}}
style={{
padding: '0.25rem 0.75rem',
backgroundColor: '#f44336',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{/* Message Modal */}
{showMessageModal && selectedMessage && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000
}}>
<div style={{
backgroundColor: 'white',
padding: '2rem',
borderRadius: '8px',
maxWidth: '600px',
width: '90%',
maxHeight: '80vh',
overflowY: 'auto'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1.5rem' }}>
<h2>Message Details</h2>
<button
onClick={() => setShowMessageModal(false)}
style={{
padding: '0.5rem 1rem',
backgroundColor: '#6c757d',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Close
</button>
</div>
<div style={{ marginBottom: '1rem' }}>
<strong>From:</strong> {selectedMessage.full_name}
</div>
<div style={{ marginBottom: '1rem' }}>
<strong>Email:</strong> {selectedMessage.email}
</div>
<div style={{ marginBottom: '1rem' }}>
<strong>Phone:</strong> {selectedMessage.phone || 'Not provided'}
</div>
<div style={{ marginBottom: '1rem' }}>
<strong>Subject:</strong> {selectedMessage.subject}
</div>
<div style={{ marginBottom: '1rem' }}>
<strong>Date:</strong> {new Date(selectedMessage.created_at).toLocaleString()}
</div>
<div style={{ marginBottom: '1.5rem' }}>
<strong>Message:</strong>
<div style={{
marginTop: '0.5rem',
padding: '1rem',
backgroundColor: '#f8f9fa',
borderRadius: '4px',
whiteSpace: 'pre-wrap'
}}>
{selectedMessage.message}
</div>
</div>
<div style={{ marginBottom: '1rem' }}>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: 'bold' }}>
Status:
</label>
<select
value={selectedMessage.status}
onChange={(e) => setSelectedMessage({ ...selectedMessage, status: e.target.value })}
style={{
width: '100%',
padding: '0.5rem',
borderRadius: '4px',
border: '1px solid #ddd'
}}
>
<option value="new">New</option>
<option value="read">Read</option>
<option value="replied">Replied</option>
</select>
</div>
<div style={{ marginBottom: '1.5rem' }}>
<label style={{ display: 'block', marginBottom: '0.5rem', fontWeight: 'bold' }}>
Admin Notes:
</label>
<textarea
value={messageNotes}
onChange={(e) => setMessageNotes(e.target.value)}
placeholder="Add internal notes about this message..."
rows="4"
style={{
width: '100%',
padding: '0.5rem',
borderRadius: '4px',
border: '1px solid #ddd',
resize: 'vertical'
}}
/>
</div>
<div style={{ display: 'flex', gap: '1rem' }}>
<button
onClick={() => handleUpdateMessage(selectedMessage.id)}
style={{
flex: 1,
padding: '0.75rem',
backgroundColor: '#28a745',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontWeight: 'bold'
}}
>
Save Changes
</button>
<button
onClick={() => {
handleDeleteMessage(selectedMessage.id)
setShowMessageModal(false)
}}
style={{
padding: '0.75rem 1.5rem',
backgroundColor: '#dc3545',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Delete
</button>
</div>
</div>
</div>
)}
</div>
</>
)}
{toast && (
<Toast
message={toast.message}
type={toast.type}
onClose={() => setToast(null)}
/>
)}
</div>
)
}