245 lines
11 KiB
PHP
245 lines
11 KiB
PHP
<?php
|
||
require __DIR__ . '/includes/db.php';
|
||
require __DIR__ . '/includes/helpers.php';
|
||
require __DIR__ . '/includes/layout.php';
|
||
|
||
$id = (int)($_GET['id'] ?? 0);
|
||
$product = null;
|
||
$medias = [];
|
||
|
||
if ($id) {
|
||
$product = $pdo->prepare("SELECT * FROM products WHERE id=?")->execute([$id])
|
||
? $pdo->query("SELECT * FROM products WHERE id=$id")->fetch()
|
||
: null;
|
||
if (!$product) { header('Location: /'); exit; }
|
||
$medias = $pdo->query("SELECT * FROM product_media WHERE product_id=$id ORDER BY sort_order")->fetchAll();
|
||
}
|
||
|
||
/* ─── Sauvegarde ─────────────────────────────────────────── */
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
$title = trim($_POST['title'] ?? '');
|
||
$desc = trim($_POST['desc'] ?? '');
|
||
$price = (float)str_replace(',','.',($_POST['price'] ?? 0));
|
||
$shipping = (float)str_replace(',','.',($_POST['shipping'] ?? 0));
|
||
$sku = trim($_POST['sku'] ?? '');
|
||
$cat_id = (int)($_POST['category_id'] ?? 0) ?: null;
|
||
$status = $_POST['status'] ?? 'brouillon';
|
||
$tags = trim($_POST['tags'] ?? '');
|
||
|
||
if (!$title) { $error = 'Le titre est obligatoire.'; goto show; }
|
||
|
||
if ($id) {
|
||
$pdo->prepare("UPDATE products SET title=?,description=?,price=?,shipping=?,sku=?,category_id=?,status=?,tags=?,updated_at=CURRENT_TIMESTAMP WHERE id=?")
|
||
->execute([$title,$desc,$price,$shipping,$sku,$cat_id,$status,$tags,$id]);
|
||
} else {
|
||
$pdo->prepare("INSERT INTO products (title,description,price,shipping,sku,category_id,status,tags) VALUES(?,?,?,?,?,?,?,?)")
|
||
->execute([$title,$desc,$price,$shipping,$sku,$cat_id,$status,$tags]);
|
||
$id = (int)$pdo->lastInsertId();
|
||
}
|
||
|
||
// Uploads médias
|
||
if (!empty($_FILES['medias']['name'][0])) {
|
||
$count = count($_FILES['medias']['name']);
|
||
for ($i = 0; $i < $count; $i++) {
|
||
if ($_FILES['medias']['error'][$i] !== UPLOAD_ERR_OK) continue;
|
||
$orig = $_FILES['medias']['name'][$i];
|
||
$ext = strtolower(pathinfo($orig, PATHINFO_EXTENSION));
|
||
if (!in_array($ext, array_merge(imgExts(), vidExts()))) continue;
|
||
$fname = uniqid('m', true) . '.' . $ext;
|
||
if (!move_uploaded_file($_FILES['medias']['tmp_name'][$i], $UPLOAD_DIR . $fname)) continue;
|
||
$type = isImage($fname) ? 'image' : 'video';
|
||
$order = (int)$pdo->query("SELECT COALESCE(MAX(sort_order),0)+1 FROM product_media WHERE product_id=$id")->fetchColumn();
|
||
$pdo->prepare("INSERT INTO product_media (product_id,type,filename,original_name,sort_order) VALUES(?,?,?,?,?)")
|
||
->execute([$id,$type,$fname,$orig,$order]);
|
||
}
|
||
}
|
||
|
||
// Suppression médias cochés
|
||
if (!empty($_POST['del_media'])) {
|
||
foreach ($_POST['del_media'] as $mid) {
|
||
$mid = (int)$mid;
|
||
$m = $pdo->query("SELECT filename FROM product_media WHERE id=$mid")->fetch();
|
||
if ($m && $m['filename']) @unlink($UPLOAD_DIR . $m['filename']);
|
||
$pdo->prepare("DELETE FROM product_media WHERE id=?")->execute([$mid]);
|
||
}
|
||
}
|
||
|
||
header('Location: /produit.php?id=' . $id);
|
||
exit;
|
||
}
|
||
|
||
show:
|
||
$cats = $pdo->query("SELECT * FROM categories ORDER BY name")->fetchAll();
|
||
$pageTitle = $id ? 'Modifier le produit' : 'Nouveau produit';
|
||
layout_head($pageTitle);
|
||
layout_sidebar($id ? '' : 'products');
|
||
layout_main_open();
|
||
?>
|
||
|
||
<div class="page-header">
|
||
<div>
|
||
<div class="page-title"><?= $id ? 'Modifier' : 'Nouveau produit' ?></div>
|
||
<?php if ($id): ?><div class="page-sub"><a href="/produit.php?id=<?= $id ?>" style="color:var(--muted);text-decoration:none">← Retour au produit</a></div><?php endif ?>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if (!empty($error)): ?><div style="background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);padding:.75rem 1rem;border-radius:8px;margin-bottom:1rem;color:var(--red)"><?= h($error) ?></div><?php endif ?>
|
||
|
||
<form method="post" enctype="multipart/form-data">
|
||
|
||
<div style="display:grid;grid-template-columns:1fr 360px;gap:1.5rem;align-items:start">
|
||
|
||
<!-- Colonne gauche -->
|
||
<div style="display:flex;flex-direction:column;gap:1rem">
|
||
|
||
<div class="card">
|
||
<div class="form-group">
|
||
<label>Titre *</label>
|
||
<input type="text" name="title" value="<?= h($product['title'] ?? '') ?>" placeholder="Nom du produit" required>
|
||
</div>
|
||
<div class="form-group" style="margin-bottom:0">
|
||
<label>Description</label>
|
||
<textarea name="desc" rows="8" placeholder="Décrivez votre produit (sera envoyé sur Etsy)…"><?= h($product['description'] ?? '') ?></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:.85rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:.75rem"><i class="fas fa-images" style="margin-right:.4rem"></i>Photos & vidéos</div>
|
||
|
||
<?php if ($medias): ?>
|
||
<div class="media-grid" id="mediaGrid" style="margin-bottom:1rem">
|
||
<?php foreach ($medias as $m): ?>
|
||
<div class="media-item<?= $m['type']==='video'?' video-item':'' ?>" data-id="<?= $m['id'] ?>">
|
||
<?php if ($m['filename']): ?>
|
||
<?php if ($m['type']==='image'): ?>
|
||
<img src="/file.php?f=<?= urlencode($m['filename']) ?>" alt="">
|
||
<?php else: ?>
|
||
<video src="/file.php?f=<?= urlencode($m['filename']) ?>" muted playsinline></video>
|
||
<?php endif ?>
|
||
<?php elseif ($m['video_url']): ?>
|
||
<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;padding:.5rem;font-size:.7rem;word-break:break-all;color:var(--muted)"><?= h($m['video_url']) ?></div>
|
||
<?php endif ?>
|
||
<span class="sort-handle" title="Drag pour réordonner"><i class="fas fa-grip-vertical"></i></span>
|
||
<label style="all:unset"><input type="checkbox" name="del_media[]" value="<?= $m['id'] ?>" style="position:absolute;top:4px;right:4px;cursor:pointer;width:16px;height:16px" title="Supprimer ce média"></label>
|
||
</div>
|
||
<?php endforeach ?>
|
||
</div>
|
||
<p class="form-hint" style="margin-bottom:.75rem"><i class="fas fa-square-check" style="margin-right:.3rem"></i>Cocher un média pour le supprimer à la sauvegarde</p>
|
||
<?php endif ?>
|
||
|
||
<div class="drop-zone" id="dropZone">
|
||
<input type="file" name="medias[]" multiple accept="image/*,video/mp4,video/webm,video/quicktime" id="fileInput">
|
||
<i class="fas fa-cloud-arrow-up" style="font-size:1.5rem;margin-bottom:.5rem;display:block"></i>
|
||
<strong>Cliquer ou glisser</strong> des images / vidéos<br>
|
||
<span style="font-size:.75rem">JPG, PNG, WEBP, AVIF, GIF, MP4, WEBM, MOV</span>
|
||
</div>
|
||
<div class="preview-strip" id="previewStrip"></div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Colonne droite -->
|
||
<div style="display:flex;flex-direction:column;gap:1rem">
|
||
|
||
<div class="card">
|
||
<div style="font-size:.85rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:.75rem"><i class="fas fa-euro-sign" style="margin-right:.4rem"></i>Prix</div>
|
||
<div class="form-row">
|
||
<div class="form-group" style="margin-bottom:0">
|
||
<label>Prix de vente</label>
|
||
<div class="input-group">
|
||
<span class="input-prefix">€</span>
|
||
<input type="number" name="price" value="<?= h((string)($product['price'] ?? '0')) ?>" step="0.01" min="0" placeholder="0.00">
|
||
</div>
|
||
</div>
|
||
<div class="form-group" style="margin-bottom:0">
|
||
<label>Frais de port</label>
|
||
<div class="input-group">
|
||
<span class="input-prefix">€</span>
|
||
<input type="number" name="shipping" value="<?= h((string)($product['shipping'] ?? '0')) ?>" step="0.01" min="0" placeholder="0.00">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:.85rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:.75rem"><i class="fas fa-layer-group" style="margin-right:.4rem"></i>Organisation</div>
|
||
<div class="form-group">
|
||
<label>Statut</label>
|
||
<select name="status">
|
||
<?php foreach (['brouillon'=>'Brouillon','prêt'=>'Prêt à publier','publié'=>'Publié'] as $v=>$l): ?>
|
||
<option value="<?= $v ?>"<?= ($product['status']??'brouillon')===$v?' selected':'' ?>><?= $l ?></option>
|
||
<?php endforeach ?>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Catégorie</label>
|
||
<select name="category_id">
|
||
<option value="">— Aucune —</option>
|
||
<?php foreach ($cats as $c): ?>
|
||
<option value="<?= $c['id'] ?>"<?= ($product['category_id']??'')==$c['id']?' selected':'' ?>><?= h($c['name']) ?></option>
|
||
<?php endforeach ?>
|
||
</select>
|
||
<div class="form-hint">Gérer les catégories dans <a href="/categories.php" style="color:var(--primary)">Catégories</a></div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Référence / SKU</label>
|
||
<input type="text" name="sku" value="<?= h($product['sku'] ?? '') ?>" placeholder="REF-001">
|
||
</div>
|
||
<div class="form-group" style="margin-bottom:0">
|
||
<label>Tags <span style="font-weight:400;text-transform:none">(séparés par des virgules)</span></label>
|
||
<input type="text" name="tags" value="<?= h($product['tags'] ?? '') ?>" placeholder="artisanat, fait main, cadeau…">
|
||
<div class="form-hint">Les tags seront utilisés lors de la publication Etsy</div>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if ($id && ($product['etsy_listing_id'] ?? '')): ?>
|
||
<div class="etsy-panel">
|
||
<div class="etsy-logo"><i class="fab fa-etsy"></i> Etsy</div>
|
||
<div class="mt-2 etsy-status-ok"><i class="fas fa-check-circle"></i> Publié — ID <?= h($product['etsy_listing_id']) ?></div>
|
||
</div>
|
||
<?php endif ?>
|
||
|
||
<div style="display:flex;gap:.75rem">
|
||
<button type="submit" class="btn btn-primary" style="flex:1"><i class="fas fa-save"></i> Sauvegarder</button>
|
||
<a href="<?= $id ? '/produit.php?id='.$id : '/' ?>" class="btn btn-secondary"><i class="fas fa-xmark"></i></a>
|
||
</div>
|
||
|
||
</div>
|
||
</div><!-- /grid -->
|
||
|
||
</form>
|
||
|
||
<script>
|
||
// Preview médias avant upload
|
||
const input = document.getElementById('fileInput');
|
||
const strip = document.getElementById('previewStrip');
|
||
const drop = document.getElementById('dropZone');
|
||
let files = [];
|
||
|
||
function addFiles(newFiles) {
|
||
[...newFiles].forEach(f => {
|
||
files.push(f);
|
||
const div = document.createElement('div');
|
||
div.className = 'preview-item';
|
||
const rm = document.createElement('button');
|
||
rm.type = 'button'; rm.className = 'rm'; rm.innerHTML = '×';
|
||
rm.onclick = () => { files = files.filter(x=>x!==f); div.remove(); syncInput(); };
|
||
const tag = f.type.startsWith('image') ? document.createElement('img') : document.createElement('video');
|
||
tag.src = URL.createObjectURL(f);
|
||
if (!f.type.startsWith('image')) { tag.muted = true; tag.loop = true; tag.autoplay = true; }
|
||
div.appendChild(tag); div.appendChild(rm); strip.appendChild(div);
|
||
});
|
||
syncInput();
|
||
}
|
||
function syncInput() {
|
||
const dt = new DataTransfer();
|
||
files.forEach(f => dt.items.add(f));
|
||
input.files = dt.files;
|
||
}
|
||
input.addEventListener('change', () => { addFiles(input.files); });
|
||
drop.addEventListener('dragover', e => { e.preventDefault(); drop.classList.add('drag-over'); });
|
||
drop.addEventListener('dragleave', () => drop.classList.remove('drag-over'));
|
||
drop.addEventListener('drop', e => { e.preventDefault(); drop.classList.remove('drag-over'); addFiles(e.dataTransfer.files); });
|
||
</script>
|
||
<?php layout_foot(); ?>
|