First commit

This commit is contained in:
2026-05-28 23:43:20 +02:00
commit 86cd7854c2
49 changed files with 8504 additions and 0 deletions
@@ -0,0 +1,240 @@
{% extends "admin/base_site.html" %}
{% load i18n static %}
{% block title %}📷 Ajout de photos — {{ property.name_fr }}{% endblock %}
{% block extrahead %}
{{ block.super }}
<style>
.upload-card {
background: #fff;
border: 1px solid #ccc;
border-radius: 6px;
padding: 24px;
max-width: 680px;
margin: 20px 0;
}
.upload-card h2 { margin-top: 0; font-size: 16px; color: #417690; }
.upload-card .form-row { margin-bottom: 16px; }
.upload-card label {
display: block;
font-weight: bold;
margin-bottom: 4px;
font-size: 13px;
}
.upload-card select,
.upload-card input[type=text],
.upload-card input[type=file] {
width: 100%;
padding: 6px 8px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 13px;
box-sizing: border-box;
}
.upload-card .help { font-size: 11px; color: #888; margin-top: 4px; }
.upload-card .separator {
text-align: center;
color: #999;
font-size: 12px;
margin: 4px 0;
position: relative;
}
.drop-zone {
border: 2px dashed #417690;
border-radius: 6px;
padding: 32px 20px;
text-align: center;
cursor: pointer;
transition: background .2s;
background: #f9fbfc;
}
.drop-zone.dragover { background: #e8f3fa; border-color: #2c6a8e; }
.drop-zone p { margin: 0; color: #417690; font-size: 14px; }
.drop-zone small { color: #888; font-size: 12px; }
#file-preview { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.preview-item {
position: relative;
width: 80px;
height: 80px;
border-radius: 4px;
overflow: hidden;
border: 1px solid #ddd;
}
.preview-item img { width: 100%; height: 100%; object-fit: cover; }
.preview-item span {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0,0,0,.55);
color: #fff;
font-size: 9px;
padding: 2px 3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.submit-row { display: flex; gap: 10px; margin-top: 20px; }
.btn-upload {
background: #417690;
color: #fff;
border: none;
padding: 10px 22px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
.btn-upload:hover { background: #2c6a8e; }
.btn-again {
background: #f0f0f0;
color: #333;
border: 1px solid #ccc;
padding: 10px 18px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
#file-count { font-size: 12px; color: #417690; margin-top: 6px; }
</style>
{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">Accueil</a>
<a href="{% url 'admin:app_list' app_label='properties' %}">Properties</a>
<a href="{% url 'admin:properties_property_changelist' %}">Biens immobiliers</a>
<a href="{% url 'admin:properties_property_change' property.pk %}">{{ property.name_fr }}</a>
Ajout de photos
</div>
{% endblock %}
{% block content %}
<h1>📷 Ajout groupé de photos — <em>{{ property.name_fr }}</em></h1>
{% if messages %}
{% for msg in messages %}
<p class="success">{{ msg }}</p>
{% endfor %}
{% endif %}
<form method="post" enctype="multipart/form-data" id="upload-form">
{% csrf_token %}
<div class="upload-card">
<h2>1. Sélectionner ou créer une pièce</h2>
<div class="form-row">
<label for="id_room">Pièce existante :</label>
<select name="room" id="id_room">
<option value="">— Photos générales (sans pièce) —</option>
{% for room in rooms %}
<option value="{{ room.pk }}">{{ room.name_fr }}</option>
{% endfor %}
</select>
</div>
<div class="separator">— ou créer une nouvelle pièce —</div>
<div class="form-row">
<label for="id_new_room_fr">Nom français de la nouvelle pièce :</label>
<input type="text" name="new_room_fr" id="id_new_room_fr"
placeholder="ex : Cuisine, Salon, Chambre 1…"
autocomplete="off">
<p class="help">Si renseigné, cette pièce est créée et les photos lui sont attribuées (prioritaire sur la sélection ci-dessus).</p>
</div>
<div class="form-row">
<label for="id_new_room_en">Nom anglais (optionnel) :</label>
<input type="text" name="new_room_en" id="id_new_room_en"
placeholder="ex : Kitchen, Living room, Bedroom 1…"
autocomplete="off">
</div>
</div>
<div class="upload-card">
<h2>2. Choisir les photos</h2>
<div class="drop-zone" id="drop-zone">
<p>📂 Glisser-déposer les photos ici</p>
<small>ou cliquer pour sélectionner (sélection multiple possible)</small>
<input type="file" name="images" id="id_images"
multiple accept="image/*"
style="position:absolute;opacity:0;width:0;height:0">
</div>
<div id="file-count"></div>
<div id="file-preview"></div>
</div>
<div class="submit-row">
<button type="submit" name="next" value="change" class="btn-upload">
✓ Télécharger et revenir à la fiche
</button>
<button type="submit" name="next" value="again" class="btn-again">
✓ Télécharger et ajouter une autre pièce
</button>
<a href="{% url 'admin:properties_property_change' property.pk %}"
style="padding:10px 16px;font-size:14px;color:#666">
Annuler
</a>
</div>
</form>
<script>
(function () {
const dropZone = document.getElementById('drop-zone');
const input = document.getElementById('id_images');
const preview = document.getElementById('file-preview');
const fileCount = document.getElementById('file-count');
let dt = new DataTransfer();
// Ouvrir le sélecteur au clic sur la zone
dropZone.addEventListener('click', () => input.click());
// Drag-and-drop
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('dragover'));
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
addFiles(e.dataTransfer.files);
});
input.addEventListener('change', () => {
addFiles(input.files);
input.value = '';
});
function addFiles(files) {
Array.from(files).forEach(f => {
if (!f.type.startsWith('image/')) return;
dt.items.add(f);
showThumb(f);
});
syncInput();
}
function showThumb(file) {
const reader = new FileReader();
reader.onload = (e) => {
const div = document.createElement('div');
div.className = 'preview-item';
div.innerHTML = `<img src="${e.target.result}" alt="${file.name}">
<span>${file.name}</span>`;
preview.appendChild(div);
};
reader.readAsDataURL(file);
}
function syncInput() {
input.files = dt.files;
const n = dt.files.length;
fileCount.textContent = n > 0 ? `${n} photo(s) sélectionnée(s)` : '';
}
})();
</script>
{% endblock %}