feat(messages): read receipts in group chat, fix group_manage 500 error
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

- Show small avatars under each message indicating who has read up to that point
- Fix BuildError: group_update → group_edit in group_manage template

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-20 11:49:55 +01:00
parent 43b48983c4
commit afaf88f43f
3 changed files with 72 additions and 2 deletions

View File

@ -268,6 +268,26 @@ def group_view(group_id):
MessageGroupMember.group_id == group_id
).order_by(MessageGroupMember.role, MessageGroupMember.joined_at).all()
# Build read receipts: for each message, find who has read up to that point
# Show avatar at the LAST message they've read (like Teams/WhatsApp)
read_receipts = {} # message_id -> list of (user, avatar_path)
if messages:
other_members = [m for m in members if m.user_id != current_user.id]
for m in other_members:
if not m.last_read_at:
continue
# Find the last message this member has read
last_read_msg = None
for msg in messages:
if msg.created_at <= m.last_read_at:
last_read_msg = msg
else:
break
if last_read_msg:
if last_read_msg.id not in read_receipts:
read_receipts[last_read_msg.id] = []
read_receipts[last_read_msg.id].append(m.user)
# Mark as read
membership.last_read_at = datetime.now()
db.commit()
@ -276,7 +296,8 @@ def group_view(group_id):
group=group,
messages=messages,
members=members,
membership=membership
membership=membership,
read_receipts=read_receipts
)
finally:
db.close()

View File

@ -350,7 +350,7 @@
{% if membership.is_owner %}
<div class="manage-card">
<h2>Informacje o grupie</h2>
<form method="POST" action="{{ url_for('messages.group_update', group_id=group.id) }}">
<form method="POST" action="{{ url_for('messages.group_edit', group_id=group.id) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group">
<label for="group-name">Nazwa grupy</label>

View File

@ -267,6 +267,42 @@
font-size: var(--font-size-sm);
}
/* Read receipts */
.msg-read-receipts {
display: flex;
gap: 2px;
justify-content: flex-end;
margin-top: 4px;
padding-right: 4px;
}
.read-receipt {
width: 20px;
height: 20px;
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
}
.read-receipt img {
width: 100%;
height: 100%;
object-fit: cover;
}
.read-receipt span {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
background: var(--primary);
color: white;
font-size: 10px;
font-weight: 600;
border-radius: 50%;
}
/* Reply form */
.reply-section {
background: var(--surface);
@ -417,6 +453,19 @@
</div>
{% endif %}
</div>
{% if read_receipts and read_receipts.get(msg.id) %}
<div class="msg-read-receipts">
{% for reader in read_receipts[msg.id] %}
<div class="read-receipt" title="{{ reader.name or reader.email.split('@')[0] }}">
{% if reader.avatar_path %}
<img src="{{ url_for('static', filename=reader.avatar_path) }}" alt="">
{% else %}
<span>{{ (reader.name or reader.email)[0].upper() }}</span>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
{% else %}