160 lines
7.0 KiB
PHP
160 lines
7.0 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../db/config.php';
|
|
$pdo = db();
|
|
|
|
$message = '';
|
|
|
|
// Handle Add Product
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_product') {
|
|
$name = $_POST['name'];
|
|
$category_id = $_POST['category_id'];
|
|
$price = $_POST['price'];
|
|
$description = $_POST['description'];
|
|
|
|
// Image handling
|
|
$image_url = 'https://placehold.co/400x300?text=' . urlencode($name);
|
|
|
|
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
|
|
$uploadDir = __DIR__ . '/../assets/images/products/';
|
|
if (!is_dir($uploadDir)) {
|
|
mkdir($uploadDir, 0755, true);
|
|
}
|
|
|
|
$fileInfo = pathinfo($_FILES['image']['name']);
|
|
$fileExt = strtolower($fileInfo['extension']);
|
|
$allowedExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
|
|
|
if (in_array($fileExt, $allowedExts)) {
|
|
$fileName = uniqid('prod_') . '.' . $fileExt;
|
|
$targetFile = $uploadDir . $fileName;
|
|
|
|
if (move_uploaded_file($_FILES['image']['tmp_name'], $targetFile)) {
|
|
$image_url = 'assets/images/products/' . $fileName;
|
|
}
|
|
}
|
|
}
|
|
|
|
$stmt = $pdo->prepare("INSERT INTO products (name, category_id, price, description, image_url) VALUES (?, ?, ?, ?, ?)");
|
|
if ($stmt->execute([$name, $category_id, $price, $description, $image_url])) {
|
|
$message = '<div class="alert alert-success">Product added successfully!</div>';
|
|
} else {
|
|
$message = '<div class="alert alert-danger">Error adding product.</div>';
|
|
}
|
|
}
|
|
|
|
// Handle Delete
|
|
if (isset($_GET['delete'])) {
|
|
$id = $_GET['delete'];
|
|
$pdo->prepare("DELETE FROM products WHERE id = ?")->execute([$id]);
|
|
header("Location: products.php");
|
|
exit;
|
|
}
|
|
|
|
// Fetch Products with Category Name
|
|
$products = $pdo->query("SELECT p.*, c.name as category_name
|
|
FROM products p
|
|
LEFT JOIN categories c ON p.category_id = c.id
|
|
ORDER BY p.id DESC")->fetchAll();
|
|
|
|
// Fetch Categories for Dropdown
|
|
$categories = $pdo->query("SELECT * FROM categories ORDER BY name")->fetchAll();
|
|
|
|
include 'includes/header.php';
|
|
?>
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h2 class="fw-bold mb-0">Products</h2>
|
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addProductModal">
|
|
<i class="bi bi-plus-lg"></i> Add Product
|
|
</button>
|
|
</div>
|
|
|
|
<?= $message ?>
|
|
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead class="bg-light">
|
|
<tr>
|
|
<th class="ps-4">Image</th>
|
|
<th>Name</th>
|
|
<th>Category</th>
|
|
<th>Price</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($products as $product): ?>
|
|
<tr>
|
|
<td class="ps-4">
|
|
<img src="<?= htmlspecialchars(strpos($product['image_url'], 'http') === 0 ? $product['image_url'] : '../' . $product['image_url']) ?>" alt="" class="rounded" width="48" height="48" style="object-fit: cover;">
|
|
</td>
|
|
<td>
|
|
<div class="fw-bold"><?= htmlspecialchars($product['name']) ?></div>
|
|
<small class="text-muted"><?= htmlspecialchars(substr($product['description'] ?? '', 0, 50)) ?>...</small>
|
|
</td>
|
|
<td><span class="badge bg-light text-dark border"><?= htmlspecialchars($product['category_name'] ?? 'Uncategorized') ?></span></td>
|
|
<td class="fw-bold"><?= format_currency($product['price']) ?></td>
|
|
<td>
|
|
<div class="btn-group">
|
|
<a href="product_edit.php?id=<?= $product['id'] ?>" class="btn btn-sm btn-outline-primary" title="Edit Product"><i class="bi bi-pencil"></i></a>
|
|
<a href="product_variants.php?product_id=<?= $product['id'] ?>" class="btn btn-sm btn-outline-secondary" title="Manage Variants"><i class="bi bi-gear"></i></a>
|
|
<a href="?delete=<?= $product['id'] ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure?')" title="Delete"><i class="bi bi-trash"></i></a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Product Modal -->
|
|
<div class="modal fade" id="addProductModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Add New Product</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<form method="POST" enctype="multipart/form-data">
|
|
<div class="modal-body">
|
|
<input type="hidden" name="action" value="add_product">
|
|
<div class="mb-3">
|
|
<label class="form-label">Name</label>
|
|
<input type="text" name="name" class="form-control" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Category</label>
|
|
<select name="category_id" class="form-select" required>
|
|
<?php foreach ($categories as $cat): ?>
|
|
<option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Price ($)</label>
|
|
<input type="number" step="0.01" name="price" class="form-control" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Description</label>
|
|
<textarea name="description" class="form-control" rows="3"></textarea>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Product Image</label>
|
|
<input type="file" name="image" class="form-control" accept="image/*">
|
|
<div class="form-text">Leave empty to use a placeholder.</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
<button type="submit" class="btn btn-primary">Save Product</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php include 'includes/footer.php'; ?>
|