feat(messages): per-member read status in group conversations — each member's name + read time or 'nieprzeczytane'
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-27 16:51:02 +01:00
parent 7bf8d6dffe
commit d5d67d8c1d
3 changed files with 66 additions and 3 deletions

View File

@ -597,6 +597,37 @@
color: #fde68a;
}
/* Group read status — vertical list */
.group-read-status {
display: flex;
flex-direction: column;
gap: 1px;
margin-left: 0;
margin-top: 4px;
}
.read-status-line {
display: block;
font-size: 10.5px;
font-weight: 500;
}
.read-status-line.read {
color: #16a34a;
}
.read-status-line.unread {
color: #eab308;
}
.message-row.mine .read-status-line.read {
color: #86efac;
}
.message-row.mine .read-status-line.unread {
color: #fde68a;
}
/* --- Legacy read checks (keep for compat) --- */
.read-check {
width: 14px;

View File

@ -642,7 +642,39 @@
}
}
if (isRead && readAt) {
if (details && details.members) {
var otherMembers = details.members.filter(function (m) {
return m.user_id !== window.__CURRENT_USER__.id;
});
var conv = state.conversations.find(function (c) { return c.id === state.currentConversationId; });
var isGroup = conv && conv.is_group;
if (isGroup) {
// Group: show per-member read status
var lines = [];
otherMembers.forEach(function (m) {
var name = m.name || 'Użytkownik';
if (m.last_read_at && new Date(m.last_read_at) >= msgTime) {
var d = new Date(m.last_read_at);
var dateStr = d.toLocaleDateString('pl', {day:'2-digit', month:'2-digit'}) + ' o ' + d.toLocaleTimeString('pl', {hour:'2-digit', minute:'2-digit'});
lines.push('<span class="read-status-line read">✓ ' + name + ' — ' + dateStr + '</span>');
} else {
lines.push('<span class="read-status-line unread">• ' + name + ' — nieprzeczytane</span>');
}
});
check.innerHTML = lines.join('');
check.classList.add('group-read-status');
} else {
// 1:1: simple read/unread
if (isRead && readAt) {
check.classList.add('read');
check.innerHTML = '✓ Przeczytane ' + readAt.toLocaleDateString('pl', {day:'2-digit', month:'2-digit'}) + ' o ' + readAt.toLocaleTimeString('pl', {hour:'2-digit', minute:'2-digit'});
} else {
check.classList.add('unread');
check.innerHTML = '• Nieprzeczytane';
}
}
} else if (isRead && readAt) {
check.classList.add('read');
check.innerHTML = '✓ Przeczytane ' + readAt.toLocaleDateString('pl', {day:'2-digit', month:'2-digit'}) + ' o ' + readAt.toLocaleTimeString('pl', {hour:'2-digit', minute:'2-digit'});
} else {

View File

@ -5,7 +5,7 @@
{% block head_extra %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/quill.snow.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/conversations.css') }}?v=8">
<link rel="stylesheet" href="{{ url_for('static', filename='css/conversations.css') }}?v=9">
<script src="{{ url_for('static', filename='js/vendor/quill.js') }}"></script>
<style>
footer { display: none !important; }
@ -228,7 +228,7 @@ window.__CSRF_TOKEN__ = '{{ csrf_token() }}';
// Load conversations.js after data is set
(function() {
var s = document.createElement('script');
s.src = '{{ url_for("static", filename="js/conversations.js") }}?v=14';
s.src = '{{ url_for("static", filename="js/conversations.js") }}?v=15';
document.body.appendChild(s);
})();
{% endblock %}