feat(messages): clear read/unread status labels — green 'Przeczytane DD.MM o HH:MM' / yellow '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:47:38 +01:00
parent b71d7b62b8
commit 7bf8d6dffe
3 changed files with 53 additions and 11 deletions

View File

@ -572,7 +572,32 @@
color: var(--conv-text-muted);
}
/* --- Read checks --- */
/* --- Read status labels --- */
.read-status {
display: inline-block;
font-size: 11px;
font-weight: 500;
margin-left: 6px;
letter-spacing: 0.1px;
}
.read-status.read {
color: #16a34a;
}
.read-status.unread {
color: #eab308;
}
.message-row.mine .read-status.read {
color: #86efac;
}
.message-row.mine .read-status.unread {
color: #fde68a;
}
/* --- Legacy read checks (keep for compat) --- */
.read-check {
width: 14px;
height: 14px;

View File

@ -616,23 +616,40 @@
},
renderReadCheck: function (msg) {
var check = el('span', 'read-check');
// Double check SVG
check.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';
var check = el('span', 'read-status');
// Check if other members have read this message
var details = state.conversationDetails[state.currentConversationId];
var isRead = false;
var readAt = null;
if (details && details.members) {
var msgTime = new Date(msg.created_at);
var allRead = details.members.every(function (m) {
if (m.user_id === window.__CURRENT_USER__.id) return true;
var otherMembers = details.members.filter(function (m) {
return m.user_id !== window.__CURRENT_USER__.id;
});
isRead = otherMembers.length > 0 && otherMembers.every(function (m) {
return m.last_read_at && new Date(m.last_read_at) >= msgTime;
});
if (allRead) {
check.classList.add('read');
check.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 6 7 17 2 12"></polyline><polyline points="22 6 11 17 9 15"></polyline></svg>';
if (isRead) {
// Find earliest read time among others
var readTimes = otherMembers
.filter(function (m) { return m.last_read_at; })
.map(function (m) { return new Date(m.last_read_at); });
if (readTimes.length) {
readAt = new Date(Math.min.apply(null, readTimes));
}
}
}
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';
}
return check;
},

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=7">
<link rel="stylesheet" href="{{ url_for('static', filename='css/conversations.css') }}?v=8">
<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=13';
s.src = '{{ url_for("static", filename="js/conversations.js") }}?v=14';
document.body.appendChild(s);
})();
{% endblock %}