Files
MaterielPartage/index.php
2025-12-25 20:12:46 +01:00

714 lines
23 KiB
PHP
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.
<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
session_start();
$db = new PDO('sqlite:db.sqlite');
$CATEGORIES = [
"Bricolage",
"Jardinage",
"Cuisine",
"Outils lourds",
"Matériel événementiel",
"Transport",
"Sport",
"Voyage",
"Autre"
];
$action = $_GET['action'] ?? 'home';
function redirect($a) {
header("Location: index.php?action=$a");
exit;
}
function is_logged() {
return isset($_SESSION['owner_id']);
}
/* ============================================================
TRAITEMENT DES FORMULAIRES
============================================================ */
/* ---------- INSCRIPTION ---------- */
if ($action === 'do_register' && $_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$stmt = $db->prepare("
INSERT INTO owners (name, address, phone, email, username, password_hash)
VALUES (?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$_POST['name'],
$_POST['address'],
$_POST['phone'],
$_POST['email'],
$_POST['username'],
password_hash($_POST['password'], PASSWORD_DEFAULT)
]);
$_SESSION['owner_id'] = $db->lastInsertId();
redirect('edit');
} catch (PDOException $e) {
$error = "Nom d'utilisateur déjà pris.";
$action = 'register';
}
}
/* ---------- CONNEXION ---------- */
if ($action === 'do_login' && $_SERVER['REQUEST_METHOD'] === 'POST') {
$stmt = $db->prepare("SELECT * FROM owners WHERE username=?");
$stmt->execute([$_POST['username']]);
$owner = $stmt->fetch(PDO::FETCH_ASSOC);
if ($owner && password_verify($_POST['password'], $owner['password_hash'])) {
$_SESSION['owner_id'] = $owner['id'];
redirect('edit');
} else {
$error = "Identifiants incorrects.";
$action = 'login';
}
}
/* ---------- DÉCONNEXION ---------- */
if ($action === 'logout') {
session_destroy();
redirect('home');
}
/* ---------- SAUVEGARDE MATERIEL (prix numérique + prix libre) ---------- */
if ($action === 'save_item' && $_SERVER['REQUEST_METHOD'] === 'POST' && is_logged()) {
// Prix numérique obligatoire
$price = floatval($_POST['price']);
if ($price <= 0) {
$error = "Le prix doit être un nombre positif.";
$action = 'edit';
}
// Prix libre ?
$price_free = isset($_POST['price_free']) ? "free" : "";
$price_value = $price_free ? "$price|free" : "$price";
// Upload photo
$photo = null;
if (!empty($_FILES['photo']['name'])) {
$photo = time() . "_" . basename($_FILES['photo']['name']);
move_uploaded_file($_FILES['photo']['tmp_name'], "uploads/" . $photo);
}
// UPDATE
if (!empty($_POST['id'])) {
if ($photo) {
$stmt = $db->prepare("UPDATE items SET name=?, price=?, description=?, photo=?, category=? WHERE id=? AND owner_id=?");
$stmt->execute([
$_POST['name'],
$price_value,
$_POST['description'],
$photo,
$_POST['category'],
$_POST['id'],
$_SESSION['owner_id']
]);
} else {
$stmt = $db->prepare("UPDATE items SET name=?, price=?, description=?, category=? WHERE id=? AND owner_id=?");
$stmt->execute([
$_POST['name'],
$price_value,
$_POST['description'],
$_POST['category'],
$_POST['id'],
$_SESSION['owner_id']
]);
}
// INSERT
} else {
$stmt = $db->prepare("INSERT INTO items (name, price, description, photo, owner_id, category)
VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([
$_POST['name'],
$price_value,
$_POST['description'],
$photo,
$_SESSION['owner_id'],
$_POST['category']
]);
}
redirect('edit');
}
/* ---------- SUPPRESSION MATERIEL ---------- */
if ($action === 'delete_item' && is_logged()) {
$stmt = $db->prepare("DELETE FROM items WHERE id=? AND owner_id=?");
$stmt->execute([$_GET['id'], $_SESSION['owner_id']]);
redirect('edit');
}
/* ---------- SUPPRESSION PHOTO MATERIEL ---------- */
if ($action === 'delete_photo' && is_logged()) {
// Récupérer le matériel
$stmt = $db->prepare("SELECT photo FROM items WHERE id=? AND owner_id=?");
$stmt->execute([$_GET['id'], $_SESSION['owner_id']]);
$item = $stmt->fetch(PDO::FETCH_ASSOC);
if ($item && !empty($item['photo'])) {
$file = "uploads/" . $item['photo'];
if (file_exists($file)) {
unlink($file);
}
// Mettre la colonne photo à NULL
$stmt = $db->prepare("UPDATE items SET photo=NULL WHERE id=? AND owner_id=?");
$stmt->execute([$_GET['id'], $_SESSION['owner_id']]);
}
redirect('edit_item&id=' . $_GET['id']);
}
/* ---------- MODIFICATION PROFIL ---------- */
if ($action === 'save_profile' && $_SERVER['REQUEST_METHOD'] === 'POST' && is_logged()) {
$params = [
$_POST['name'],
$_POST['address'],
$_POST['phone'],
$_POST['email'],
$_POST['username']
];
$sql = "UPDATE owners SET name=?, address=?, phone=?, email=?, username=?";
if (!empty($_POST['password'])) {
$sql .= ", password_hash=?";
$params[] = password_hash($_POST['password'], PASSWORD_DEFAULT);
}
$sql .= " WHERE id=?";
$params[] = $_SESSION['owner_id'];
$stmt = $db->prepare($sql);
$stmt->execute($params);
$success = "Profil mis à jour.";
$action = 'profile';
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Location de matériel</title>
<link rel="stylesheet" href="pico.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { max-width: 900px; margin: 40px auto; }
.item { padding: 1rem; border: 1px solid #ddd; border-radius: 8px; margin-bottom: 1.5rem; }
.tool-row { display: flex; gap: 1.5rem; align-items: flex-start; }
.tool-row img { width: 180px; border-radius: 8px; object-fit: cover; }
.tool-info { flex: 1; }
.owner-block { margin-top: .5rem; font-size: .9rem; color: #555; }
</style>
</head>
<body style="padding: 0 10px">
<h1 style="display:flex; align-items:center; justify-content:space-between;">
<span>Matériel partagé à Durban-sur-Arize</span>
<img src="uploads/blason-durban.png" alt="Blason de Durban-sur-Arize"
style="height:48px; width:auto; margin-left:1rem;">
</h1>
<nav>
<a href="index.php">Accueil</a>
<?php if (is_logged()): ?>
<a href="index.php?action=edit">Mon matériel</a>
<a href="index.php?action=profile">Mon profil</a>
<a href="index.php?action=logout">Déconnexion</a>
<?php else: ?>
<a href="index.php?action=login">Connexion</a>
<a href="index.php?action=register">Inscription</a>
<?php endif; ?>
</nav>
<hr>
<?php
/* ============================================================
VUE : ACCUEIL (liste publique)
============================================================ */
if ($action === 'home') {
?>
<div id="price-customizer" style="
margin-bottom: 1.2rem;
padding: .6rem .8rem;
border: 1px solid var(--pico-muted-border-color);
border-radius: .5rem;
font-size: .9rem;
">
<form id="income-form" style="
display: flex;
align-items: center;
gap: .4rem;
flex-wrap: wrap;
margin: 0;
">
<strong>Personnaliser les indications de prix libre</strong>
<span>selon mon revenu mensuel :</span>
<input
type="number"
id="income"
value="1900"
min="0"
step="1"
style="width: 110px; margin: 0;">
<span>€/mois</span>
<small style="font-size: 0.8em">Le prix conseillé par défaut est pour le revenu médian de 1900 €, en fonction du revenu le montant indiqué est modifié.</small>
</form>
</div>
<div id="search-tools" style="
margin-bottom: 1.2rem;
padding: .6rem .8rem;
border: 1px solid var(--pico-muted-border-color);
border-radius: .5rem;
font-size: .9rem;
">
<form style="display:flex; align-items:center; gap:.6rem; flex-wrap:wrap; margin:0;">
<strong style="font-size:.95rem;">Rechercher un matériel :</strong>
<input
type="text"
id="search"
placeholder="Nom ou description…"
style="flex:1; min-width:200px; margin:0;"
>
</form>
</div>
<?php
$items = $db->query("
SELECT items.*, owners.name AS owner_name, owners.phone, owners.address, owners.email
FROM items
JOIN owners ON owners.id = items.owner_id
ORDER BY items.category COLLATE NOCASE ASC,
items.name COLLATE NOCASE ASC
")->fetchAll(PDO::FETCH_ASSOC);
foreach ($items as $item) {
// Décodage du prix
$price_raw = $item['price'] ?? '';
$price_parts = explode('|', $price_raw);
$price_value = floatval($price_parts[0]);
$price_free = isset($price_parts[1]) && $price_parts[1] === 'free';
$search_text = strtolower(
($item['name'] ?? '') . ' ' . ($item['description'] ?? '')
);
echo "<article class='item' data-search='" . htmlspecialchars($search_text, ENT_QUOTES) . "'>";
echo "<div class='tool-row'>";
// Photo
if (!empty($item['photo'])) {
echo "<img src='uploads/{$item['photo']}' alt='Photo'>";
} else {
echo "<div style='
width:180px;
height:120px;
display:flex;
align-items:center;
justify-content:center;
font-size:60px;
'>🛠️</div>";
}
echo "<div class='tool-info'>";
echo "<h2 style='margin: 0;'>" . htmlspecialchars($item['name'] ?? '') . "</h2>";
echo "<p style='opacity:.7; font-size:.85rem;'>Catégorie : " . htmlspecialchars($item['category']) . "</p>";
// Prix
echo "<p class='price-block' data-base-price='{$price_value}' data-free='{$price_free}'>";
echo "<strong>Prix :</strong> {$price_value} € / jour";
if ($price_free) echo " <em>(prix libre)</em>";
echo "</p>";
// Description
echo "<p>" . nl2br(htmlspecialchars($item['description'] ?? '')) . "</p>";
// Propriétaire
echo "<div class='owner-block'>";
echo "<strong>Propriétaire :</strong> " . htmlspecialchars($item['owner_name'] ?? '') . "<br>";
// Adresse
$addr = urlencode($item['address'] ?? '');
echo htmlspecialchars($item['address'] ?? '')."<br>";
// Téléphone → tel:
echo "<a href='tel:" . htmlspecialchars($item['phone'] ?? '') . "'>"
. htmlspecialchars($item['phone'] ?? '')
. "</a><br>";
// Email → mailto:
echo "<a href='mailto:" . htmlspecialchars($item['email'] ?? '') . "'>"
. htmlspecialchars($item['email'] ?? '')
. "</a>";
echo "</div>"; // owner-block
echo "</div>"; // tool-info
echo "</div>"; // tool-row
echo "</article>";
}
}
/* ============================================================
VUE : CONNEXION
============================================================ */
if ($action === 'login') {
echo "<h2>Connexion</h2>";
if (!empty($error)) echo "<p style='color:red'>$error</p>";
?>
<form method="post" action="index.php?action=do_login">
<input name="username" placeholder="Nom d'utilisateur" required>
<input name="password" type="password" placeholder="Mot de passe" required>
<button>Connexion</button>
</form>
<?php
}
/* ============================================================
VUE : INSCRIPTION
============================================================ */
if ($action === 'register') {
echo "<h2>Inscription</h2>";
if (!empty($error)) echo "<p style='color:red'>$error</p>";
?>
<form method="post" action="index.php?action=do_register">
<input name="name" placeholder="Nom complet" required>
<input name="address" placeholder="Code postal">
<input name="phone" placeholder="Téléphone">
<input name="email" placeholder="Email">
<input name="username" placeholder="Nom d'utilisateur" required>
<input name="password" type="password" placeholder="Mot de passe" required>
<button>Créer mon compte</button>
</form>
<?php
}
/* ============================================================
VUE : PROFIL
============================================================ */
if ($action === 'profile' && is_logged()) {
$stmt = $db->prepare("SELECT * FROM owners WHERE id=?");
$stmt->execute([$_SESSION['owner_id']]);
$owner = $stmt->fetch(PDO::FETCH_ASSOC);
echo "<h2>Mon profil</h2>";
if (!empty($success)) echo "<p style='color:green'>$success</p>";
?>
<form method="post" action="index.php?action=save_profile">
<input name="name" value="<?= htmlspecialchars($owner['name'] ?? '') ?>" placeholder="Nom complet" required>
<input name="address" value="<?= htmlspecialchars($owner['address'] ?? '') ?>" placeholder="Code postal">
<input name="phone" value="<?= htmlspecialchars($owner['phone'] ?? '') ?>" placeholder="Téléphone">
<input name="email" value="<?= htmlspecialchars($owner['email'] ?? '') ?>" placeholder="Email">
<input name="username" value="<?= htmlspecialchars($owner['username'] ?? '') ?>" placeholder="Nom d'utilisateur" required>
<input name="password" type="password" placeholder="Nouveau mot de passe (laisser vide)">
<button>Mettre à jour</button>
</form>
<?php
}
/* ============================================================
VUE : ÉDITION DUN MATERIEL
============================================================ */
if ($action === 'edit_item' && is_logged()) {
$stmt = $db->prepare("SELECT * FROM items WHERE id=? AND owner_id=?");
$stmt->execute([$_GET['id'], $_SESSION['owner_id']]);
$item = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$item) {
echo "<p>Matériel introuvable.</p>";
} else {
// Décodage du prix
$price_raw = $item['price'] ?? '';
$price_parts = explode('|', $price_raw);
$price_value = floatval($price_parts[0]);
$price_free = isset($price_parts[1]) && $price_parts[1] === 'free';
echo "<h2>Modifier le matériel</h2>";
?>
<form method="post" enctype="multipart/form-data" action="index.php?action=save_item">
<input type="hidden" name="id" value="<?= $item['id'] ?>">
<label>Nom du matériel</label>
<input name="name" value="<?= htmlspecialchars($item['name']) ?>" required>
<label>Prix par jour (€)</label>
<input name="price" type="number" step="0.01" min="0"
value="<?= $price_value ?>" required>
<p style="font-size:.85rem; opacity:.8;">
Le prix libre conseillé sera automatiquement ajusté selon le revenu de lutilisateur.
</p>
<label>
<input type="checkbox" name="price_free" <?= $price_free ? 'checked' : '' ?>>
Prix libre (le prix devient indicatif)
</label>
<label>Catégorie</label>
<select name="category" required>
<option value="">Choisir…</option>
<?php foreach ($CATEGORIES as $cat): ?>
<option value="<?= htmlspecialchars($cat) ?>"
<?= ($item['category'] === $cat ? 'selected' : '') ?>>
<?= htmlspecialchars($cat) ?>
</option>
<?php endforeach; ?>
</select>
<label>Description</label>
<textarea name="description"><?= htmlspecialchars($item['description']) ?></textarea>
<label>Photo actuelle</label><br>
<?php if ($item['photo']): ?>
<img src="uploads/<?= $item['photo'] ?>" style="max-width:150px;border-radius:6px;">
<br>
<a href="index.php?action=delete_photo&id=<?= $item['id'] ?>"
onclick="return confirm('Supprimer cette photo ?')"
style="font-size:.85rem; color:var(--pico-del-color);">
Supprimer la photo
</a>
<?php else: ?>
<div style='
width:150px;
height:100px;
display:flex;
align-items:center;
justify-content:center;
font-size:50px;
'>🛠️</div>
<?php endif; ?>
<br><br>
<label>Nouvelle photo (optionnel)</label>
<input type="file" name="photo">
<button>Mettre à jour</button>
</form>
<?php
}
}
/* ============================================================
VUE : GESTION DES MATERIELS
============================================================ */
if ($action === 'edit' && is_logged()) {
echo "<h2>Mes matériels</h2>";
?>
<h3 style="cursor:pointer;" id="toggle-form"> Ajouter un matériel</h3>
<div id="tool-form" style="display:none; margin-bottom:2rem;">
<form method="post" enctype="multipart/form-data" action="index.php?action=save_item">
<input type="hidden" name="id">
<label>Nom du matériel</label>
<input name="name" placeholder="Nom" required>
<label>Prix par jour (€)</label>
<input name="price" type="number" step="0.01" min="0" placeholder="Prix" required>
<p style="font-size:.85rem; opacity:.8;">
Le prix libre conseillé sera automatiquement ajusté selon le revenu de lutilisateur.
</p>
<label>
<input type="checkbox" name="price_free">
Prix libre (le prix devient indicatif)
</label>
<label>Catégorie</label>
<select name="category" required>
<option value="">Choisir…</option>
<?php foreach ($CATEGORIES as $cat): ?>
<option value="<?= htmlspecialchars($cat) ?>"><?= htmlspecialchars($cat) ?></option>
<?php endforeach; ?>
</select>
<label>Description</label>
<textarea name="description" placeholder="Description"></textarea>
<label>Photo</label>
<input type="file" name="photo">
<button>Enregistrer</button>
</form>
</div>
<?php
/* Liste des matériels existants */
$stmt = $db->prepare("SELECT * FROM items WHERE owner_id=? ORDER BY category COLLATE NOCASE ASC,
name COLLATE NOCASE ASC
");
$stmt->execute([$_SESSION['owner_id']]);
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "<h3>Mes matériels existants</h3>";
foreach ($items as $item) {
// Décodage du prix
$price_raw = $item['price'] ?? '';
$price_parts = explode('|', $price_raw);
$price_value = floatval($price_parts[0]);
$price_free = isset($price_parts[1]) && $price_parts[1] === 'free';
echo "<article class='item'>";
echo "<div class='tool-row'>";
// Photo
if (!empty($item['photo'])) {
echo "<img src='uploads/{$item['photo']}' alt='Photo'>";
} else {
echo "<div style='
width:180px;
height:120px;
display:flex;
align-items:center;
justify-content:center;
font-size:60px;
'>🛠️</div>";
}
echo "<div class='tool-info'>";
echo "<h3 style='margin: 0;'>" . htmlspecialchars($item['name'] ?? '') . "</h3>";
echo "<p style='opacity:.7; font-size:.85rem;'>Catégorie : " . htmlspecialchars($item['category']) . "</p>";
echo "<p><strong>Prix :</strong> {$price_value} € / jour";
if ($price_free) echo " <em>(prix libre)</em>";
echo "</p>";
echo "<p>" . nl2br(htmlspecialchars($item['description'] ?? '')) . "</p>";
echo "<footer>";
echo "<a href='index.php?action=edit_item&id={$item['id']}'>Modifier</a> | ";
echo "<a href='index.php?action=delete_item&id={$item['id']}' onclick='return confirm(\"Supprimer ce matériel ?\")'>Supprimer</a>";
echo "</footer>";
echo "</div>"; // tool-info
echo "</div>"; // tool-row
echo "</article>";
}
}
?>
<script>
// -----------------------------
// Recalcul des prix libres
// -----------------------------
function updateFreePrices() {
const incomeInput = document.getElementById('income');
if (!incomeInput) return; // Pas de champ -> rien à faire
const income = parseFloat(incomeInput.value) || 1900;
const ratio = income / 1900;
document.querySelectorAll('.price-block').forEach(block => {
const base = parseFloat(block.dataset.basePrice);
const isFree = block.dataset.free === "1" || block.dataset.free === "true";
if (!isFree) return;
const newPrice = (base * ratio).toFixed(2);
block.innerHTML = `<strong>Prix :</strong> ${newPrice} € / jour <em>(prix libre)</em>`;
});
}
// Mise à jour en direct si le champ existe
const incomeInput = document.getElementById('income');
if (incomeInput) {
incomeInput.addEventListener('input', updateFreePrices);
updateFreePrices(); // Mise à jour initiale
}
// Empêcher Enter dans le formulaire income
const incomeForm = document.getElementById('income-form');
if (incomeForm) {
incomeForm.addEventListener('keydown', function (e) {
if (e.key === "Enter") e.preventDefault();
});
}
// -----------------------------
// Recherche dynamique
// -----------------------------
function updateSearch() {
const searchInput = document.getElementById('search');
if (!searchInput) return;
const query = searchInput.value.toLowerCase().trim();
const words = query.split(/\s+/).filter(w => w.length > 0);
document.querySelectorAll('article.item').forEach(item => {
const text = item.dataset.search || "";
const match = words.every(w => text.includes(w));
item.style.display = match ? "" : "none";
});
}
const searchInput = document.getElementById('search');
if (searchInput) {
searchInput.addEventListener('input', updateSearch);
searchInput.addEventListener('keydown', function (e) {
if (e.key === "Enter") e.preventDefault();
});
}
// -----------------------------
// Dépliage du formulaire matériel
// -----------------------------
const toggleForm = document.getElementById('toggle-form');
const toolForm = document.getElementById('tool-form');
if (toggleForm && toolForm) {
toggleForm.addEventListener('click', function () {
toolForm.style.display =
(toolForm.style.display === "none" || toolForm.style.display === "")
? "block"
: "none";
});
}
</script>
<footer style="margin-top:2rem; text-align:center; opacity:.7; font-size:.85rem;">
Problème ou question :
<a href="mailto:leo@kabano.org">Léo / leo@kabano.org</a>
</footer>
</body>
</html>