241 lines
9.1 KiB
HTML
241 lines
9.1 KiB
HTML
{% extends "mobile/mobile_base.html" %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}{% translate "Notifications" %}{% endblock title %}
|
|
|
|
{% block extra_head %}
|
|
<style>
|
|
.notif-card {
|
|
border-left: 4px solid transparent;
|
|
}
|
|
|
|
.notif-card.unread {
|
|
border-left-color: var(--brand-color, #2f3e8b);
|
|
background-color: #f8faff;
|
|
}
|
|
|
|
.notif-meta {
|
|
font-size: 0.78rem;
|
|
}
|
|
|
|
.notif-type-pill {
|
|
font-size: 0.72rem;
|
|
}
|
|
</style>
|
|
{% endblock extra_head %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h1 class="h5 mb-0">
|
|
{% translate "Notifications" %}
|
|
{% if unread_count > 0 %}
|
|
<span class="badge rounded-pill ms-1" style="background-color: var(--brand-color, #2f3e8b);">{{ unread_count }}</span>
|
|
{% endif %}
|
|
</h1>
|
|
<a href="{% url 'mobile:mobile_index' %}" class="btn btn-outline-secondary btn-sm">
|
|
<i class="bi bi-house me-1"></i>{% translate "Accueil" %}
|
|
</a>
|
|
</div>
|
|
|
|
<div class="card border-0 shadow-sm mb-3">
|
|
<div class="card-body py-2">
|
|
<form method="get" class="row g-2 align-items-center">
|
|
<div class="col-12">
|
|
<select name="type" class="form-select form-select-sm" onchange="this.form.submit()">
|
|
<option value="">{% translate "Tous les types" %}</option>
|
|
{% for val, label in notification_types %}
|
|
<option value="{{ val }}" {% if filter_type == val %}selected{% endif %}>{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-12 d-flex justify-content-between align-items-center">
|
|
<div class="form-check form-switch mb-0">
|
|
<input class="form-check-input" type="checkbox" id="showArchived"
|
|
name="archived" value="1"
|
|
{% if show_archived %}checked{% endif %}
|
|
onchange="this.form.submit()">
|
|
<label class="form-check-label" for="showArchived">{% translate "Archivées" %}</label>
|
|
</div>
|
|
{% if filter_type or show_archived %}
|
|
<a href="{% url 'mobile:notifications_mobile' %}" class="btn btn-link btn-sm text-muted p-0">
|
|
{% translate "Réinitialiser" %}
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
{% if unread_count > 0 %}
|
|
<div class="mb-3">
|
|
<button type="button" class="btn btn-sm btn-outline-secondary w-100" id="mark-all-read-btn">
|
|
<i class="bi bi-check2-all me-1"></i>{% translate "Tout marquer comme lu" %}
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if notifications %}
|
|
<div class="d-flex flex-column gap-2">
|
|
{% for item in notifications %}
|
|
{% with notif=item.notification %}
|
|
<div class="card notif-card {% if not item.is_read and not item.is_archived %}unread{% endif %}"
|
|
id="notif-row-{{ item.recipient_id }}-{{ item.recipient_type }}">
|
|
<div class="card-body py-2">
|
|
<div class="d-flex justify-content-between gap-2">
|
|
<div class="flex-grow-1">
|
|
<div class="d-flex align-items-center gap-2 mb-1">
|
|
<i class="bi {{ notif.type_icon }} text-{{ notif.type_color }}"></i>
|
|
<strong class="small">{{ notif.title }}</strong>
|
|
{% if not item.is_read and not item.is_archived %}
|
|
<span class="badge bg-primary notif-type-pill">{% translate "Nouveau" %}</span>
|
|
{% endif %}
|
|
</div>
|
|
{% if notif.content %}
|
|
<p class="text-muted small mb-2">{{ notif.content|linebreaksbr }}</p>
|
|
{% endif %}
|
|
<div class="d-flex flex-wrap gap-2 align-items-center notif-meta text-muted">
|
|
<span>{{ notif.created_at|date:"d/m/Y H:i" }}</span>
|
|
{% if item.team_name %}
|
|
<span class="badge bg-secondary notif-type-pill">{{ item.team_name }}</span>
|
|
{% endif %}
|
|
{% if item.mobile_action_url %}
|
|
<a href="{{ item.mobile_action_url }}" class="btn btn-sm btn-outline-primary py-0 px-2">
|
|
{{ notif.action_label|default:item.mobile_action_url }}
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="d-flex flex-column align-items-end gap-2">
|
|
{% if not item.is_read %}
|
|
<button type="button"
|
|
class="btn btn-sm btn-outline-success mark-read-btn"
|
|
data-pk="{{ item.recipient_id }}"
|
|
data-type="{{ item.recipient_type }}">
|
|
{% if notif.is_single_claim %}
|
|
{% translate "Prendre" %}
|
|
{% else %}
|
|
{% translate "Lu" %}
|
|
{% endif %}
|
|
</button>
|
|
{% endif %}
|
|
{% if item.recipient_type == 'personal' and not item.is_archived %}
|
|
<button type="button"
|
|
class="btn btn-sm btn-outline-secondary archive-btn"
|
|
data-pk="{{ item.recipient_id }}"
|
|
aria-label="{% translate 'Archiver' %}">
|
|
<i class="bi bi-archive"></i>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endwith %}
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-5 text-muted">
|
|
<i class="bi bi-bell-slash" style="font-size: 2.5rem;"></i>
|
|
<p class="mt-2 mb-0">
|
|
{% if show_archived %}
|
|
{% translate "Aucune notification archivée." %}
|
|
{% else %}
|
|
{% translate "Vous n'avez aucune notification." %}
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
{% endif %}
|
|
{% endblock content %}
|
|
|
|
{% block extra_scripts %}
|
|
<script>
|
|
(() => {
|
|
const csrfToken = "{{ csrf_token }}";
|
|
const markReadBase = "{% url 'notifications:mark_read' 0 %}";
|
|
const archiveBase = "{% url 'notifications:archive' 0 %}";
|
|
const markAllUrl = "{% url 'notifications:mark_all_read' %}";
|
|
|
|
function updateBadge(count) {
|
|
const badges = document.querySelectorAll('#mobile-navbar-notif-badge, #navbar-notif-badge');
|
|
badges.forEach((badge) => {
|
|
if (!badge) return;
|
|
if (count > 0) {
|
|
badge.textContent = count;
|
|
badge.style.display = '';
|
|
} else {
|
|
badge.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
async function postAction(url, body = null) {
|
|
const options = {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': csrfToken,
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
};
|
|
if (body) {
|
|
options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
options.body = body;
|
|
}
|
|
const response = await fetch(url, options);
|
|
if (!response.ok) {
|
|
throw new Error('request_failed');
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
document.querySelectorAll('.mark-read-btn').forEach((button) => {
|
|
button.addEventListener('click', async () => {
|
|
button.disabled = true;
|
|
const pk = button.dataset.pk;
|
|
const type = button.dataset.type;
|
|
try {
|
|
const data = await postAction(markReadBase.replace('0', pk), `type=${encodeURIComponent(type)}`);
|
|
const row = document.getElementById(`notif-row-${pk}-${type}`);
|
|
if (row) {
|
|
row.classList.remove('unread');
|
|
}
|
|
button.remove();
|
|
updateBadge(data.unread_count);
|
|
} catch (error) {
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.archive-btn').forEach((button) => {
|
|
button.addEventListener('click', async () => {
|
|
button.disabled = true;
|
|
const pk = button.dataset.pk;
|
|
try {
|
|
const data = await postAction(archiveBase.replace('0', pk));
|
|
const row = document.getElementById(`notif-row-${pk}-personal`);
|
|
if (row) {
|
|
row.remove();
|
|
}
|
|
updateBadge(data.unread_count);
|
|
} catch (error) {
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
});
|
|
|
|
const markAllButton = document.getElementById('mark-all-read-btn');
|
|
if (markAllButton) {
|
|
markAllButton.addEventListener('click', async () => {
|
|
markAllButton.disabled = true;
|
|
try {
|
|
const data = await postAction(markAllUrl);
|
|
updateBadge(data.unread_count || 0);
|
|
window.location.reload();
|
|
} catch (error) {
|
|
markAllButton.disabled = false;
|
|
}
|
|
});
|
|
}
|
|
})();
|
|
</script>
|
|
{% endblock extra_scripts %}
|