Init PercoVitrine — app de gestion de fiches produits

This commit is contained in:
perco
2026-06-27 10:34:47 +02:00
commit a1b3f6da98
16 changed files with 1158 additions and 0 deletions
+244
View File
@@ -0,0 +1,244 @@
<?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(); ?>