353 lines
14 KiB
HTML
353 lines
14 KiB
HTML
{% extends "base.html" %}
|
|
{% load i18n static property_tags %}
|
|
|
|
{% block title %}{{ property.seo_title }}{% endblock %}
|
|
{% block meta_description %}{{ property.seo_description }}{% endblock %}
|
|
{% block meta_keywords_tag %}<meta name="keywords" content="{{ property.seo_keywords }}">{% endblock %}
|
|
{% block canonical %}{{ request.build_absolute_uri }}{% endblock %}
|
|
{% block og_title %}{{ property.seo_title }}{% endblock %}
|
|
{% block og_description %}{{ property.seo_description }}{% endblock %}
|
|
{% block og_type %}website{% endblock %}
|
|
{% block og_image_tag %}{% if property.thumbnail %}<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{{ property.thumbnail.url }}">{% elif property.cover_photo %}<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{{ property.cover_photo.image.url }}">{% endif %}{% endblock %}
|
|
{% block extra_head %}
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "RealEstateListing",
|
|
"name": "{{ property.name|escapejs }}",
|
|
"description": "{{ property.seo_description|escapejs }}",
|
|
"url": "{{ request.build_absolute_uri }}",
|
|
{% if property.thumbnail %}"image": "{{ request.scheme }}://{{ request.get_host }}{{ property.thumbnail.url }}",{% elif property.cover_photo %}"image": "{{ request.scheme }}://{{ request.get_host }}{{ property.cover_photo.image.url }}",{% endif %}
|
|
"address": {
|
|
"@type": "PostalAddress",
|
|
"streetAddress": "{{ property.address|escapejs }}",
|
|
"addressLocality": "{{ property.city|escapejs }}",
|
|
"postalCode": "{{ property.postal_code|escapejs }}",
|
|
"addressCountry": "FR"
|
|
}{% if not property.price_on_request %},
|
|
"offers": {
|
|
"@type": "Offer",
|
|
"price": "{{ property.price }}",
|
|
"priceCurrency": "EUR"
|
|
}{% endif %}
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="detail-page">
|
|
|
|
{# ── En-tête du bien ───────────────────────────────────────────────────── #}
|
|
<section class="detail-header container">
|
|
<div class="detail-header-meta">
|
|
<a href="{% url 'properties:list' %}" class="back-link">
|
|
← {% trans "Retour aux annonces" %}
|
|
</a>
|
|
<span class="detail-status
|
|
{% if property.status == 'sale' %}status-sale{% else %}status-rent{% endif %}">
|
|
{% if property.status == 'sale' %}{% trans "À vendre" %}{% else %}{% trans "À louer" %}{% endif %}
|
|
</span>
|
|
</div>
|
|
<h1 class="detail-title">{{ property.name }}</h1>
|
|
<p class="detail-location">
|
|
📍 {{ property.address }}, {{ property.city }} ({{ property.postal_code }})
|
|
</p>
|
|
<p class="detail-price">
|
|
{% if property.price_on_request %}
|
|
{% trans "Prix sur demande" %}
|
|
{% else %}
|
|
{{ property.price|price_fr }}
|
|
{% if property.status == 'rent' %}<small>{% trans "/ mois" %}</small>{% endif %}
|
|
{% endif %}
|
|
</p>
|
|
</section>
|
|
|
|
{# ── Galerie photos ────────────────────────────────────────────────────── #}
|
|
<section class="detail-gallery container" id="gallery-section">
|
|
|
|
{# Onglets par pièce #}
|
|
{% if rooms_with_photos or general_photos %}
|
|
<div class="gallery-tabs" role="tablist" aria-label="{% trans 'Photos par pièce' %}">
|
|
{% if general_photos %}
|
|
<button class="gallery-tab active" role="tab" aria-selected="true"
|
|
data-room="general" aria-controls="room-general">
|
|
{% trans "Toutes" %}
|
|
</button>
|
|
{% endif %}
|
|
{% for room in rooms_with_photos %}
|
|
{% if room.photos.all %}
|
|
<button class="gallery-tab {% if not general_photos and forloop.first %}active{% endif %}"
|
|
role="tab" aria-selected="{% if not general_photos and forloop.first %}true{% else %}false{% endif %}"
|
|
data-room="{{ room.pk }}" aria-controls="room-{{ room.pk }}">
|
|
{{ room.name }}
|
|
</button>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
|
|
{# Grille photos — général #}
|
|
{% if general_photos %}
|
|
<div class="gallery-grid active" id="room-general" role="tabpanel">
|
|
{% for photo in general_photos %}
|
|
<button class="gallery-item" data-gallery="main" data-index="{{ forloop.counter0 }}"
|
|
aria-label="{{ photo.caption|default:property.name }}">
|
|
<img src="{{ photo.image.url }}" alt="{{ photo.caption|default:property.name }}"
|
|
loading="lazy" class="gallery-thumb">
|
|
{% if photo.caption %}<span class="gallery-item-caption">{{ photo.caption }}</span>{% endif %}
|
|
</button>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Grille photos — par pièce #}
|
|
{% for room in rooms_with_photos %}
|
|
{% if room.photos.all %}
|
|
<div class="gallery-grid {% if not general_photos and forloop.first %}active{% endif %}"
|
|
id="room-{{ room.pk }}" role="tabpanel">
|
|
{% for photo in room.photos.all %}
|
|
<button class="gallery-item" data-gallery="room-{{ room.pk }}" data-index="{{ forloop.counter0 }}"
|
|
aria-label="{{ photo.caption|default:room.name }}">
|
|
<img src="{{ photo.image.url }}" alt="{{ photo.caption|default:room.name }}"
|
|
loading="lazy" class="gallery-thumb">
|
|
{% if photo.caption %}<span class="gallery-item-caption">{{ photo.caption }}</span>{% endif %}
|
|
</button>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% else %}
|
|
<p class="no-photos">{% trans "Aucune photo disponible." %}</p>
|
|
{% endif %}
|
|
|
|
</section>
|
|
|
|
{# ── Corps — infos + description ───────────────────────────────────────── #}
|
|
<div class="detail-body container">
|
|
<div class="detail-main">
|
|
|
|
{# Description #}
|
|
<section class="detail-section">
|
|
<h2 class="section-title">{% trans "Description" %}</h2>
|
|
<div class="detail-description">
|
|
{{ property.description|linebreaks }}
|
|
</div>
|
|
</section>
|
|
|
|
{# Environnement #}
|
|
{% if property.environment %}
|
|
<section class="detail-section">
|
|
<h2 class="section-title">{% trans "Situation géographique" %}</h2>
|
|
|
|
<p class="env-type">
|
|
{% with ltype=property.environment.location_type %}
|
|
{% if ltype == 'city' %}🏙 {% trans "Environnement urbain" %}
|
|
{% elif ltype == 'village' %}🏘 {% trans "Village" %}
|
|
{% else %}🌿 {% trans "Campagne" %}
|
|
{% endif %}
|
|
{% endwith %}
|
|
</p>
|
|
|
|
{% if property.environment.description %}
|
|
<p class="env-description">{{ property.environment.description }}</p>
|
|
{% endif %}
|
|
|
|
<div class="env-services">
|
|
<div class="env-group">
|
|
<h3 class="env-group-title">🛒 {% trans "Commerces de proximité" %}</h3>
|
|
<ul class="env-list">
|
|
{% for icon, label, available in property.environment.get_nearby_shops %}
|
|
<li class="env-item {% if available %}env-available{% else %}env-unavailable{% endif %}">
|
|
<span class="env-icon">{{ icon }}</span>
|
|
{{ label }}
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="env-group">
|
|
<h3 class="env-group-title">🏛️ {% trans "Services publics" %}</h3>
|
|
<ul class="env-list">
|
|
{% for icon, label, available in property.environment.get_public_services %}
|
|
<li class="env-item {% if available %}env-available{% else %}env-unavailable{% endif %}">
|
|
<span class="env-icon">{{ icon }}</span>
|
|
{{ label }}
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="env-group">
|
|
<h3 class="env-group-title">🩺 {% trans "Santé" %}</h3>
|
|
<ul class="env-list">
|
|
{% for icon, label, available in property.environment.get_health_services %}
|
|
<li class="env-item {% if available %}env-available{% else %}env-unavailable{% endif %}">
|
|
<span class="env-icon">{{ icon }}</span>
|
|
{{ label }}
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
</section>
|
|
{% endif %}
|
|
|
|
</div>{# /detail-main #}
|
|
|
|
{# ── Fiche récapitulative (sidebar) ──────────────────────────────────── #}
|
|
<aside class="detail-sidebar">
|
|
|
|
<div class="sidebar-card">
|
|
<h2 class="sidebar-title">{% trans "Informations clés" %}</h2>
|
|
<dl class="key-info">
|
|
<dt>{% trans "Type de bien" %}</dt>
|
|
<dd>{{ property.get_property_type_display }}</dd>
|
|
|
|
<dt>{% trans "Surface habitable" %}</dt>
|
|
<dd>{{ property.living_area }} m²</dd>
|
|
|
|
<dt>{% trans "Surface totale" %}</dt>
|
|
<dd>{{ property.total_area }} m²</dd>
|
|
|
|
{% if property.total_land_area %}
|
|
<dt>{% trans "Surface terrain" %}</dt>
|
|
<dd>{{ property.total_land_area }} m²</dd>
|
|
{% endif %}
|
|
|
|
<dt>{% trans "Nombre de pièces" %}</dt>
|
|
<dd>{{ property.rooms_count }}</dd>
|
|
|
|
<dt>{% trans "Chambres" %}</dt>
|
|
<dd>{{ property.bedrooms_count }}</dd>
|
|
|
|
<dt>{% trans "Salles d'eau" %}</dt>
|
|
<dd>{{ property.bathrooms_count }}</dd>
|
|
|
|
{% if property.exposure %}
|
|
<dt>{% trans "Exposition" %}</dt>
|
|
<dd>{{ property.get_exposure_display }}</dd>
|
|
{% endif %}
|
|
|
|
{% if property.construction_year %}
|
|
<dt>{% trans "Année de construction" %}</dt>
|
|
<dd>{{ property.construction_year }}</dd>
|
|
{% endif %}
|
|
|
|
<dt>{% trans "État du bien" %}</dt>
|
|
<dd>{{ property.get_condition_display }}</dd>
|
|
|
|
<dt>{% trans "Ascenseur" %}</dt>
|
|
<dd>{% if property.has_elevator %}{% trans "Oui" %}{% else %}{% trans "Non" %}{% endif %}</dd>
|
|
</dl>
|
|
|
|
{# Extérieurs #}
|
|
{% if property.has_garden or property.has_pool or property.has_dependencies %}
|
|
<div class="exterior-badges">
|
|
{% if property.has_garden %}
|
|
<span class="badge badge-exterior">🌳 {% trans "Jardin" %}</span>
|
|
{% endif %}
|
|
{% if property.has_pool %}
|
|
<span class="badge badge-exterior">🏊 {% trans "Piscine" %}</span>
|
|
{% endif %}
|
|
{% if property.has_dependencies %}
|
|
<span class="badge badge-exterior">🏚 {% trans "Dépendances" %}</span>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Caractéristiques libres #}
|
|
{% if property.features.all %}
|
|
<div class="features-list">
|
|
<h3 class="features-title">{% trans "Caractéristiques" %}</h3>
|
|
<ul>
|
|
{% for feat in property.features.all %}
|
|
<li>
|
|
{% if feat.icon %}<span>{{ feat.icon }}</span> {% endif %}
|
|
{{ feat.name }}
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# Diagnostics DPE / GES #}
|
|
{% if property.energy_class or property.ges_class %}
|
|
<div class="sidebar-card diagnostics-card">
|
|
<h2 class="sidebar-title">{% trans "Diagnostics" %}</h2>
|
|
|
|
{% if property.energy_class %}
|
|
<div class="diag-row">
|
|
<span class="diag-label">{% trans "Classe énergie (DPE)" %}</span>
|
|
{% dpe_badge property.energy_class property.energy_value "kWh/m²/an" %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if property.ges_class %}
|
|
<div class="diag-row">
|
|
<span class="diag-label">{% trans "Émissions GES" %}</span>
|
|
{% dpe_badge property.ges_class property.ges_value "kgCO₂/m²/an" %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="dpe-scale-wrapper">
|
|
{% dpe_scale property.energy_class property.ges_class %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Bouton de contact #}
|
|
<div class="sidebar-card contact-card">
|
|
<h2 class="sidebar-title">{% trans "Renseignements" %}</h2>
|
|
<p class="contact-intro">
|
|
{% trans "Vous souhaitez visiter ce bien ou obtenir plus d'informations ?" %}
|
|
</p>
|
|
<button class="btn btn-primary btn-full" id="open-contact-modal"
|
|
data-property="{{ property.pk }}">
|
|
✉ {% trans "Envoyer un message" %}
|
|
</button>
|
|
</div>
|
|
|
|
</aside>
|
|
</div>{# /detail-body #}
|
|
|
|
</div>{# /detail-page #}
|
|
|
|
{# ── Modal de contact ─────────────────────────────────────────────────────── #}
|
|
{% include "properties/partials/contact_modal.html" %}
|
|
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Données des galeries transmises au JS
|
|
const GALLERY_DATA = {
|
|
{% if general_photos %}
|
|
'main': [
|
|
{% for photo in general_photos %}
|
|
{ src: '{{ photo.image.url }}', caption: '{{ photo.caption|escapejs }}' }{% if not forloop.last %},{% endif %}
|
|
{% endfor %}
|
|
],
|
|
{% endif %}
|
|
{% for room in rooms_with_photos %}
|
|
{% if room.photos.all %}
|
|
'room-{{ room.pk }}': [
|
|
{% for photo in room.photos.all %}
|
|
{ src: '{{ photo.image.url }}', caption: '{{ photo.caption|escapejs }}' }{% if not forloop.last %},{% endif %}
|
|
{% endfor %}
|
|
]{% if not forloop.last %},{% endif %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
};
|
|
const CONTACT_URL = '{% url "properties:contact" %}';
|
|
const CSRF_TOKEN = '{{ csrf_token }}';
|
|
const I18N = {
|
|
of: '{% trans "sur" %}',
|
|
sending: '{% trans "Envoi en cours…" %}',
|
|
send: '{% trans "Envoyer" %}',
|
|
successTitle: '{% trans "Message envoyé !" %}',
|
|
successMsg: '{% trans "Nous vous répondrons dans les plus brefs délais." %}',
|
|
};
|
|
</script>
|
|
{% endblock %}
|