180 lines
7 KiB
HTML
180 lines
7 KiB
HTML
{% extends "base.html" %}
|
|
{% load i18n %}
|
|
{% load static %}
|
|
|
|
{% block head %}
|
|
<style>
|
|
.thread-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
.msg-bubble {
|
|
max-width: 80%;
|
|
display: inline-block;
|
|
word-break: break-word;
|
|
}
|
|
.msg-mine .msg-bubble {
|
|
background: var(--brand-color, #2f3e8b);
|
|
color: #fff;
|
|
border-radius: 16px 16px 4px 16px;
|
|
}
|
|
.msg-other .msg-bubble {
|
|
background: #f1f3f5;
|
|
color: #212529;
|
|
border-radius: 16px 16px 16px 4px;
|
|
}
|
|
.msg-mine { align-items: flex-end; }
|
|
.msg-other { align-items: flex-start; }
|
|
.avatar-xs {
|
|
width: 32px;
|
|
height: 32px;
|
|
font-size: 0.75rem;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
background: #6c757d;
|
|
color: #fff;
|
|
}
|
|
#thread-scroll {
|
|
max-height: 60vh;
|
|
overflow-y: auto;
|
|
}
|
|
.attachment-thumb {
|
|
width: 60px;
|
|
height: 60px;
|
|
object-fit: cover;
|
|
border-radius: 6px;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container py-4" style="max-width:860px;">
|
|
|
|
<!-- Breadcrumb / Header -->
|
|
<div class="d-flex align-items-center gap-2 mb-3">
|
|
<a href="{% url 'notifications:discussion_list' %}" class="btn btn-sm btn-outline-secondary">
|
|
<i class="bi bi-arrow-left"></i>
|
|
</a>
|
|
<div class="flex-grow-1">
|
|
<h1 class="h4 mb-0">{{ discussion.title|default:discussion }}</h1>
|
|
{% if discussion.object_label %}
|
|
<a href="{{ discussion.object_url }}" class="small text-muted">
|
|
<i class="bi bi-link-45deg me-1"></i>{{ discussion.object_label }}
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
{% if discussion.is_closed %}
|
|
<span class="badge bg-secondary"><i class="bi bi-lock me-1"></i>{% translate "Clôturée" %}</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Participants -->
|
|
<div class="mb-3 d-flex flex-wrap gap-1">
|
|
{% for p in discussion.participants.all %}
|
|
<span class="badge bg-light text-dark border">
|
|
<i class="bi bi-person me-1"></i>{{ p.get_full_name|default:p.username }}
|
|
</span>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Messages -->
|
|
<div class="card shadow-sm mb-3">
|
|
<div class="card-body p-3" id="thread-scroll">
|
|
{% if not thread_messages %}
|
|
<p class="text-muted text-center py-4 mb-0">
|
|
<i class="bi bi-chat-square d-block fs-2 mb-2"></i>
|
|
{% translate "Aucun message pour l'instant. Soyez le premier à écrire." %}
|
|
</p>
|
|
{% else %}
|
|
<div class="thread-container">
|
|
{% for msg in thread_messages %}
|
|
<div class="d-flex flex-column {% if msg.author == request.user %}msg-mine{% else %}msg-other{% endif %}">
|
|
{% if msg.author != request.user %}
|
|
<div class="d-flex align-items-end gap-2">
|
|
<div class="avatar-xs">
|
|
{{ msg.author.get_short_name|first|upper|default:msg.author.username|first|upper }}
|
|
</div>
|
|
{% endif %}
|
|
<div>
|
|
{% if msg.author != request.user %}
|
|
<div class="small text-muted mb-1">
|
|
<strong>{{ msg.author.get_full_name|default:msg.author.username }}</strong>
|
|
· {{ msg.created_at|date:"d/m/Y H:i" }}
|
|
</div>
|
|
{% else %}
|
|
<div class="small text-muted mb-1 text-end">
|
|
{{ msg.created_at|date:"d/m/Y H:i" }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="msg-bubble p-3">
|
|
{{ msg.content|linebreaksbr }}
|
|
{% if msg.attachments.exists %}
|
|
<div class="mt-2 d-flex flex-wrap gap-2">
|
|
{% for att in msg.attachments.all %}
|
|
{% if att.is_image %}
|
|
<a href="{{ att.file.url }}" target="_blank">
|
|
<img src="{{ att.file.url }}" class="attachment-thumb" alt="{{ att.name }}">
|
|
</a>
|
|
{% else %}
|
|
<a href="{{ att.file.url }}" target="_blank"
|
|
class="btn btn-sm {% if msg.author == request.user %}btn-outline-light{% else %}btn-outline-secondary{% endif %}">
|
|
<i class="bi bi-paperclip me-1"></i>{{ att.name|truncatechars:24 }}
|
|
</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% if msg.author != request.user %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Reply form -->
|
|
{% if not discussion.is_closed %}
|
|
<form method="post" enctype="multipart/form-data" action="">
|
|
{% csrf_token %}
|
|
<div class="card shadow-sm">
|
|
<div class="card-body p-3">
|
|
<textarea name="content" class="form-control mb-2" rows="3"
|
|
placeholder="{% translate 'Répondre...' %}" required id="reply-textarea"></textarea>
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<label class="btn btn-outline-secondary btn-sm mb-0" id="attach-label">
|
|
<i class="bi bi-paperclip me-1"></i>{% translate "Joindre" %}
|
|
<input type="file" name="attachments" multiple class="d-none" id="attach-input"
|
|
onchange="document.getElementById('attach-count').textContent = this.files.length ? '(' + this.files.length + ')' : ''">
|
|
</label>
|
|
<span id="attach-count" class="small text-muted"></span>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="bi bi-send me-1"></i>{% translate "Envoyer" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
{% else %}
|
|
<div class="alert alert-secondary"><i class="bi bi-lock me-2"></i>{% translate "Cette discussion est clôturée." %}</div>
|
|
{% endif %}
|
|
|
|
</div>
|
|
{% endblock content %}
|
|
|
|
{% block script %}
|
|
<script>
|
|
// Scroll to bottom of thread on load
|
|
const threadScroll = document.getElementById('thread-scroll');
|
|
if (threadScroll) {
|
|
threadScroll.scrollTop = threadScroll.scrollHeight;
|
|
}
|
|
</script>
|
|
{% endblock %}
|