Premier commit

This commit is contained in:
root
2025-12-25 18:54:48 +00:00
commit 948e4ee79d
6 changed files with 748 additions and 0 deletions

714
index.php Normal file
View File

@@ -0,0 +1,714 @@
<?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">
<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>
<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>
</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>