Files

394 lines
19 KiB
PHP
Raw Permalink 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.
<?php
ob_start();
/**
* Tom's Java Jive - Admin Product Edit/Add
*/
$pageTitle = 'Product';
require_once __DIR__ . '/includes/header.php';
// Load categories and product types for dropdowns
$categoriesList = db()->fetchAll("SELECT category_id, name FROM categories WHERE is_active = 1 ORDER BY name ASC");
$productTypesList = db()->fetchAll("SELECT type_id, name FROM product_types WHERE is_active = 1 ORDER BY sort_order ASC, name ASC");
$productId = $_GET['id'] ?? '';
$product = null;
$isEdit = false;
$errors = [];
if ($productId) {
$product = db()->fetch("SELECT * FROM products WHERE product_id = :id", ['id' => $productId]);
if ($product) {
$isEdit = true;
$pageTitle = 'Edit Product';
$product['images'] = json_decode($product['images'] ?? '[]', true);
$product['tags'] = json_decode($product['tags'] ?? '[]', true);
}
}
if (!$product) {
$product = [
'product_id' => '',
'name' => '',
'description' => '',
'price' => '',
'sale_price' => '',
'cost_price' => '',
'sku' => '',
'barcode' => '',
'category' => '',
'tags' => [],
'images' => [],
'stock' => 100,
'low_stock_threshold' => 10,
'weight' => '',
'is_active' => 1,
'is_featured' => 0
];
$pageTitle = 'Add Product';
}
// Handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$description = trim($_POST['description'] ?? '');
$price = floatval($_POST['price'] ?? 0);
$salePrice = !empty($_POST['sale_price']) ? floatval($_POST['sale_price']) : null;
$costPrice = !empty($_POST['cost_price']) ? floatval($_POST['cost_price']) : null;
$sku = trim($_POST['sku'] ?? '');
$barcode = trim($_POST['barcode'] ?? '');
$category = trim($_POST['category'] ?? '');
$stock = intval($_POST['stock'] ?? 0);
$lowStockThreshold = intval($_POST['low_stock_threshold'] ?? 10);
$weight = !empty($_POST['weight']) ? floatval($_POST['weight']) : null;
$isActive = isset($_POST['is_active']) ? 1 : 0;
$isFeatured = isset($_POST['is_featured']) ? 1 : 0;
$imageUrls = array_filter(array_map('trim', explode("\n", $_POST['image_urls'] ?? '')));
// Validate
if (empty($name)) $errors['name'] = 'Product name is required';
if ($price <= 0) $errors['price'] = 'Price must be greater than 0';
if (empty($errors)) {
$data = [
'name' => $name,
'description' => $description,
'price' => $price,
'sale_price' => $salePrice,
'cost_price' => $costPrice,
'sku' => $sku ?: null,
'barcode' => $barcode ?: null,
'category' => $category ?: null,
'product_type_id' => trim($_POST['product_type_id'] ?? '') ?: null,
'images' => json_encode($imageUrls),
'stock' => $stock,
'low_stock_threshold' => $lowStockThreshold,
'weight' => $weight,
'is_active' => $isActive,
'is_featured' => $isFeatured
];
if ($isEdit) {
db()->update('products', $data, 'product_id = :id', ['id' => $productId]);
setFlash('success', 'Product updated successfully');
} else {
$data['product_id'] = generateId('prod_');
db()->insert('products', $data);
setFlash('success', 'Product created successfully');
}
header('Location: /admin/products.php');
exit;
}
// Keep form values on error
$product = array_merge($product, $_POST);
$product['images'] = $imageUrls;
}
// Get categories for dropdown
$categories = db()->fetchAll(
"SELECT DISTINCT category FROM products WHERE category IS NOT NULL AND category != '' ORDER BY category"
);
?>
<div class="page-header">
<h1 class="page-title"><?= $isEdit ? 'Edit' : 'Add' ?> Product</h1>
<a href="/admin/products.php" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Back to Products
</a>
</div>
<?php if (!empty($errors)): ?>
<div class="alert alert-error">
<i class="fas fa-exclamation-circle"></i>
Please fix the errors below
</div>
<?php endif; ?>
<form method="POST" action="">
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem; align-items: start;">
<!-- Main Info -->
<div>
<div class="admin-card">
<div class="admin-card-header">
<h3 class="admin-card-title">Basic Information</h3>
</div>
<div class="admin-card-body">
<div class="form-group">
<label class="form-label">Product Name *</label>
<input type="text" name="name" class="form-input"
value="<?= htmlspecialchars($product['name']) ?>" required>
<?php if (isset($errors['name'])): ?>
<span class="form-error"><?= $errors['name'] ?></span>
<?php endif; ?>
</div>
<div class="form-group">
<label class="form-label">Description</label>
<textarea name="description" class="form-textarea" rows="4"><?= htmlspecialchars($product['description']) ?></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Category</label>
<select name="category" class="form-input">
<option value="">-- Select Category --</option>
<?php foreach ($categoriesList as $cat): ?>
<option value="<?= htmlspecialchars($cat['name']) ?>"
<?= ($product['category'] ?? '') === $cat['name'] ? 'selected' : '' ?>>
<?= htmlspecialchars($cat['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label class="form-label">Product Type</label>
<select name="product_type_id" class="form-input">
<option value="">-- Select Type --</option>
<?php foreach ($productTypesList as $pt): ?>
<option value="<?= htmlspecialchars($pt['type_id']) ?>"
<?= ($product['product_type_id'] ?? '') === $pt['type_id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($pt['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label class="form-label">Weight (oz)</label>
<input type="number" name="weight" class="form-input" step="0.01"
value="<?= htmlspecialchars($product['weight']) ?>">
</div>
</div>
</div>
</div>
<div class="admin-card">
<div class="admin-card-header">
<h3 class="admin-card-title">Pricing</h3>
</div>
<div class="admin-card-body">
<div class="form-row">
<div class="form-group">
<label class="form-label">Price *</label>
<input type="number" name="price" class="form-input" step="0.01" min="0"
value="<?= htmlspecialchars($product['price']) ?>" required>
<?php if (isset($errors['price'])): ?>
<span class="form-error"><?= $errors['price'] ?></span>
<?php endif; ?>
</div>
<div class="form-group">
<label class="form-label">Sale Price</label>
<input type="number" name="sale_price" class="form-input" step="0.01" min="0"
value="<?= htmlspecialchars($product['sale_price'] ?? '') ?>">
</div>
</div>
<div class="form-group">
<label class="form-label">Cost Price (for profit tracking)</label>
<input type="number" name="cost_price" class="form-input" step="0.01" min="0"
value="<?= htmlspecialchars($product['cost_price'] ?? '') ?>">
</div>
</div>
</div>
<div class="admin-card">
<div class="admin-card-header">
<h3 class="admin-card-title">Images</h3>
</div>
<div class="admin-card-body">
<div class="form-group">
<label class="form-label">Product Images</label>
<div id="dropZone" onclick="document.getElementById('imgInput').click()"
style="border:2px dashed var(--color-border);border-radius:var(--radius-md);padding:2rem;text-align:center;cursor:pointer;background:var(--color-background);margin-bottom:1rem">
<i class="fas fa-cloud-upload-alt" style="font-size:2rem;color:var(--color-text-muted);display:block;margin-bottom:.5rem"></i>
<div style="font-weight:600;margin-bottom:.25rem">Drag &amp; drop images here</div>
<div style="font-size:.875rem;color:var(--color-text-muted)">or click to browse — JPG, PNG, WebP, GIF up to 5MB each</div>
<input type="file" id="imgInput" multiple accept="image/jpeg,image/png,image/gif,image/webp" style="display:none">
</div>
<div id="imgPreviews" style="display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:1rem"></div>
<div style="display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;color:var(--color-text-muted);font-size:.875rem">
<hr style="flex:1;border:none;border-top:1px solid var(--color-border)">
<span>or add image URLs</span>
<hr style="flex:1;border:none;border-top:1px solid var(--color-border)">
</div>
<textarea name="image_urls" id="imageUrls" class="form-textarea" rows="3"
placeholder="https://example.com/image1.jpg"><?= htmlspecialchars(is_array($product['images']) ? implode("\n", $product['images']) : ($product['images'] ?? '')) ?></textarea>
<small style="color:var(--color-text-muted)">One URL per line</small>
</div>
<script>
(function() {
var zone = document.getElementById('dropZone');
var input = document.getElementById('imgInput');
zone.addEventListener('dragover', function(e) {
e.preventDefault();
zone.style.borderColor = 'var(--color-primary)';
zone.style.background = 'rgba(255,94,26,.05)';
});
zone.addEventListener('dragleave', function() {
zone.style.borderColor = 'var(--color-border)';
zone.style.background = 'var(--color-background)';
});
zone.addEventListener('drop', function(e) {
e.preventDefault();
zone.style.borderColor = 'var(--color-border)';
zone.style.background = 'var(--color-background)';
processFiles(e.dataTransfer.files);
});
input.addEventListener('change', function() {
processFiles(this.files);
this.value = '';
});
function processFiles(files) {
Array.from(files).forEach(function(file) {
if (!file.type.match(/^image\/(jpeg|png|gif|webp)$/)) {
alert(file.name + ': unsupported type');
return;
}
if (file.size > 5 * 1024 * 1024) {
alert(file.name + ': exceeds 5MB limit');
return;
}
uploadFile(file);
});
}
function uploadFile(file) {
var wrap = document.createElement('div');
wrap.style.cssText = 'position:relative;width:100px;height:100px;border-radius:6px;overflow:hidden;border:1px solid var(--color-border);background:#111;display:flex;align-items:center;justify-content:center';
wrap.innerHTML = '<i class="fas fa-spinner fa-spin" style="color:#fff;font-size:1.25rem"></i>';
document.getElementById('imgPreviews').appendChild(wrap);
var fd = new FormData();
fd.append('image', file);
fetch('/admin/upload-image.php', {method: 'POST', credentials: 'same-origin', body: fd})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.url) {
wrap.innerHTML =
'<img src="' + data.url + '" style="width:100%;height:100%;object-fit:cover">' +
'<button type="button" data-url="' + data.url + '" style="position:absolute;top:2px;right:2px;width:20px;height:20px;border-radius:50%;background:rgba(0,0,0,.7);border:none;color:#fff;cursor:pointer;font-size:12px;line-height:1">×</button>';
wrap.querySelector('button').addEventListener('click', function() {
var url = this.getAttribute('data-url');
wrap.remove();
var ta = document.getElementById('imageUrls');
ta.value = ta.value.split('\n').filter(function(u) { return u.trim() !== url; }).join('\n');
});
var ta = document.getElementById('imageUrls');
ta.value = (ta.value.trim() ? ta.value.trim() + '\n' : '') + data.url;
} else {
wrap.innerHTML = '<div style="padding:.5rem;font-size:.7rem;color:#f87171;text-align:center">' + (data.error || 'Failed') + '</div>';
}
})
.catch(function() {
wrap.innerHTML = '<div style="padding:.5rem;font-size:.7rem;color:#f87171;text-align:center">Upload failed</div>';
});
}
})();
</script>
</div>
</div>
</div>
<!-- Sidebar -->
<div>
<div class="admin-card">
<div class="admin-card-header">
<h3 class="admin-card-title">Status</h3>
</div>
<div class="admin-card-body">
<div class="form-group">
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<input type="checkbox" name="is_active" value="1"
<?= $product['is_active'] ? 'checked' : '' ?>>
Active (visible in store)
</label>
</div>
<div class="form-group mb-0">
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
<input type="checkbox" name="is_featured" value="1"
<?= $product['is_featured'] ? 'checked' : '' ?>>
Featured product
</label>
</div>
</div>
</div>
<div class="admin-card">
<div class="admin-card-header">
<h3 class="admin-card-title">Inventory</h3>
</div>
<div class="admin-card-body">
<div class="form-group">
<label class="form-label">Stock Quantity</label>
<input type="number" name="stock" class="form-input" min="0"
value="<?= htmlspecialchars($product['stock']) ?>">
</div>
<div class="form-group mb-0">
<label class="form-label">Low Stock Alert Threshold</label>
<input type="number" name="low_stock_threshold" class="form-input" min="0"
value="<?= htmlspecialchars($product['low_stock_threshold']) ?>">
</div>
</div>
</div>
<div class="admin-card">
<div class="admin-card-header">
<h3 class="admin-card-title">Identifiers</h3>
</div>
<div class="admin-card-body">
<div class="form-group">
<label class="form-label">SKU</label>
<input type="text" name="sku" class="form-input"
value="<?= htmlspecialchars($product['sku'] ?? '') ?>">
</div>
<div class="form-group mb-0">
<label class="form-label">Barcode</label>
<input type="text" name="barcode" class="form-input"
value="<?= htmlspecialchars($product['barcode'] ?? '') ?>">
</div>
</div>
</div>
<button type="submit" class="btn btn-primary btn-lg btn-block">
<i class="fas fa-save"></i> <?= $isEdit ? 'Update' : 'Create' ?> Product
</button>
</div>
</div>
</form>
<?php require_once __DIR__ . '/includes/footer.php'; ?>