improve(messages): add "Nowa grupa" button with dedicated group creation modal
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
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
New button next to "Nowa wiadomość" with outline style. Modal includes group name field, multi-member search picker, and optional first message. Uses the existing POST /api/conversations endpoint with is_group=true. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0c9ea2e69f
commit
ca75468367
@ -160,6 +160,15 @@
|
|||||||
background: var(--conv-primary-hover);
|
background: var(--conv-primary-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-new-group {
|
||||||
|
background: var(--conv-surface);
|
||||||
|
color: var(--conv-primary);
|
||||||
|
border: 1.5px solid var(--conv-primary);
|
||||||
|
}
|
||||||
|
.btn-new-group:hover {
|
||||||
|
background: var(--conv-primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.btn-new-text { display: none; }
|
.btn-new-text { display: none; }
|
||||||
.btn-new-conversation { width: 36px; padding: 0; }
|
.btn-new-conversation { width: 36px; padding: 0; }
|
||||||
|
|||||||
@ -2201,6 +2201,246 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 10b. NEW GROUP MODAL
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
var NewGroupModal = {
|
||||||
|
_quill: null,
|
||||||
|
_selectedMembers: [],
|
||||||
|
_debounceTimer: null,
|
||||||
|
|
||||||
|
init: function () {
|
||||||
|
var btn = document.getElementById('newGroupBtn');
|
||||||
|
var modal = document.getElementById('newGroupModal');
|
||||||
|
if (!btn || !modal) return;
|
||||||
|
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
NewGroupModal.open();
|
||||||
|
});
|
||||||
|
|
||||||
|
var closeBtn = document.getElementById('closeNewGroup');
|
||||||
|
var cancelBtn = document.getElementById('cancelNewGroup');
|
||||||
|
if (closeBtn) closeBtn.addEventListener('click', function () { modal.style.display = 'none'; });
|
||||||
|
if (cancelBtn) cancelBtn.addEventListener('click', function () { modal.style.display = 'none'; });
|
||||||
|
|
||||||
|
var sendBtn = document.getElementById('sendNewGroup');
|
||||||
|
if (sendBtn) sendBtn.addEventListener('click', function () { NewGroupModal.send(); });
|
||||||
|
|
||||||
|
var searchInput = document.getElementById('groupRecipientSearch');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.addEventListener('input', function () {
|
||||||
|
clearTimeout(NewGroupModal._debounceTimer);
|
||||||
|
NewGroupModal._debounceTimer = setTimeout(function () {
|
||||||
|
NewGroupModal.filterRecipients(searchInput.value);
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
open: function () {
|
||||||
|
NewGroupModal._selectedMembers = [];
|
||||||
|
var nameInput = document.getElementById('groupNameInput');
|
||||||
|
if (nameInput) nameInput.value = '';
|
||||||
|
var searchInput = document.getElementById('groupRecipientSearch');
|
||||||
|
if (searchInput) searchInput.value = '';
|
||||||
|
var suggestions = document.getElementById('groupRecipientSuggestions');
|
||||||
|
if (suggestions) suggestions.innerHTML = '';
|
||||||
|
var selected = document.getElementById('groupSelectedRecipients');
|
||||||
|
if (selected) selected.innerHTML = '';
|
||||||
|
|
||||||
|
var editorEl = document.getElementById('groupMessageEditor');
|
||||||
|
if (editorEl) {
|
||||||
|
editorEl.innerHTML = '';
|
||||||
|
NewGroupModal._quill = new Quill('#groupMessageEditor', {
|
||||||
|
theme: 'snow',
|
||||||
|
placeholder: 'Pierwsza wiadomość (opcjonalna)...',
|
||||||
|
modules: {
|
||||||
|
toolbar: [['bold', 'italic'], ['link'], ['clean']],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var modal = document.getElementById('newGroupModal');
|
||||||
|
if (modal) modal.style.display = 'flex';
|
||||||
|
if (nameInput) nameInput.focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
filterRecipients: async function (query) {
|
||||||
|
var suggestions = document.getElementById('groupRecipientSuggestions');
|
||||||
|
if (!suggestions) return;
|
||||||
|
suggestions.innerHTML = '';
|
||||||
|
|
||||||
|
query = (query || '').trim();
|
||||||
|
if (query.length < 2) return;
|
||||||
|
|
||||||
|
suggestions.innerHTML = '<div style="padding:10px 12px;color:var(--conv-text-muted);font-size:13px">Szukam...</div>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
var resp = await fetch('/api/users/search?q=' + encodeURIComponent(query), {
|
||||||
|
headers: { 'X-CSRFToken': window.__CSRF_TOKEN__ },
|
||||||
|
});
|
||||||
|
if (!resp.ok) throw new Error('search failed');
|
||||||
|
var users = await resp.json();
|
||||||
|
|
||||||
|
suggestions.innerHTML = '';
|
||||||
|
var selectedIds = NewGroupModal._selectedMembers.map(function (r) { return r.id; });
|
||||||
|
var matches = users.filter(function (u) {
|
||||||
|
return selectedIds.indexOf(u.id) === -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!matches.length) {
|
||||||
|
suggestions.innerHTML = '<div style="padding:10px 12px;color:var(--conv-text-muted);font-size:13px">Brak wyników</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches.forEach(function (u) {
|
||||||
|
var item = el('div', 'suggestion-item');
|
||||||
|
item.style.padding = '8px 12px';
|
||||||
|
item.style.cursor = 'pointer';
|
||||||
|
item.style.display = 'flex';
|
||||||
|
item.style.alignItems = 'center';
|
||||||
|
item.style.gap = '8px';
|
||||||
|
item.style.borderBottom = '1px solid var(--conv-border)';
|
||||||
|
|
||||||
|
var avatar = el('div', 'conv-avatar ' + avatarColor(u.name));
|
||||||
|
avatar.style.width = '30px';
|
||||||
|
avatar.style.height = '30px';
|
||||||
|
avatar.style.minWidth = '30px';
|
||||||
|
avatar.style.fontSize = '11px';
|
||||||
|
avatar.textContent = initials(u.name);
|
||||||
|
|
||||||
|
var info = el('div', '');
|
||||||
|
var nameEl = el('div', '', u.name || u.email);
|
||||||
|
nameEl.style.fontSize = '14px';
|
||||||
|
nameEl.style.fontWeight = '500';
|
||||||
|
if (u.company_name) {
|
||||||
|
var companyEl = el('div', '', u.company_name);
|
||||||
|
companyEl.style.fontSize = '12px';
|
||||||
|
companyEl.style.color = 'var(--conv-text-muted)';
|
||||||
|
info.appendChild(nameEl);
|
||||||
|
info.appendChild(companyEl);
|
||||||
|
} else {
|
||||||
|
info.appendChild(nameEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.appendChild(avatar);
|
||||||
|
item.appendChild(info);
|
||||||
|
|
||||||
|
item.addEventListener('click', function () {
|
||||||
|
NewGroupModal.selectMember(u);
|
||||||
|
});
|
||||||
|
item.addEventListener('mouseenter', function () {
|
||||||
|
item.style.background = 'var(--conv-surface-secondary)';
|
||||||
|
});
|
||||||
|
item.addEventListener('mouseleave', function () {
|
||||||
|
item.style.background = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
suggestions.appendChild(item);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
suggestions.innerHTML = '<div style="padding:10px 12px;color:var(--conv-text-muted);font-size:13px">Błąd wyszukiwania</div>';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
selectMember: function (user) {
|
||||||
|
NewGroupModal._selectedMembers.push(user);
|
||||||
|
|
||||||
|
var container = document.getElementById('groupSelectedRecipients');
|
||||||
|
if (container) {
|
||||||
|
var pill = el('span', '');
|
||||||
|
pill.style.display = 'inline-flex';
|
||||||
|
pill.style.alignItems = 'center';
|
||||||
|
pill.style.gap = '4px';
|
||||||
|
pill.style.padding = '4px 10px';
|
||||||
|
pill.style.borderRadius = '16px';
|
||||||
|
pill.style.background = 'var(--conv-primary-light)';
|
||||||
|
pill.style.fontSize = '13px';
|
||||||
|
pill.style.color = 'var(--conv-text-primary)';
|
||||||
|
pill.style.margin = '2px';
|
||||||
|
|
||||||
|
pill.textContent = user.name || user.email;
|
||||||
|
var removeBtn = el('button', '', '\u00d7');
|
||||||
|
removeBtn.style.border = 'none';
|
||||||
|
removeBtn.style.background = 'transparent';
|
||||||
|
removeBtn.style.cursor = 'pointer';
|
||||||
|
removeBtn.style.fontSize = '14px';
|
||||||
|
removeBtn.style.color = 'var(--conv-text-muted)';
|
||||||
|
removeBtn.style.padding = '0';
|
||||||
|
removeBtn.style.lineHeight = '1';
|
||||||
|
removeBtn.addEventListener('click', function () {
|
||||||
|
NewGroupModal._selectedMembers = NewGroupModal._selectedMembers.filter(function (r) {
|
||||||
|
return r.id !== user.id;
|
||||||
|
});
|
||||||
|
pill.remove();
|
||||||
|
});
|
||||||
|
pill.appendChild(removeBtn);
|
||||||
|
container.appendChild(pill);
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchInput = document.getElementById('groupRecipientSearch');
|
||||||
|
if (searchInput) searchInput.value = '';
|
||||||
|
var suggestions = document.getElementById('groupRecipientSuggestions');
|
||||||
|
if (suggestions) suggestions.innerHTML = '';
|
||||||
|
if (searchInput) searchInput.focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
send: async function () {
|
||||||
|
var nameInput = document.getElementById('groupNameInput');
|
||||||
|
var groupName = (nameInput ? nameInput.value : '').trim();
|
||||||
|
|
||||||
|
if (NewGroupModal._selectedMembers.length < 2) {
|
||||||
|
alert('Wybierz co najmniej dwóch członków grupy');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state._isCreating) return;
|
||||||
|
state._isCreating = true;
|
||||||
|
|
||||||
|
var messageContent = '';
|
||||||
|
if (NewGroupModal._quill) {
|
||||||
|
var text = NewGroupModal._quill.getText().trim();
|
||||||
|
if (text) {
|
||||||
|
messageContent = NewGroupModal._quill.root.innerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var memberIds = NewGroupModal._selectedMembers.map(function (r) { return r.id; });
|
||||||
|
|
||||||
|
// Auto-generate name if not provided
|
||||||
|
if (!groupName) {
|
||||||
|
var names = NewGroupModal._selectedMembers.map(function (r) { return r.name || r.email; });
|
||||||
|
names.push(window.__CURRENT_USER__.name);
|
||||||
|
groupName = names.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var result = await api('/api/conversations', 'POST', {
|
||||||
|
member_ids: memberIds,
|
||||||
|
name: groupName,
|
||||||
|
message: messageContent,
|
||||||
|
});
|
||||||
|
|
||||||
|
var modal = document.getElementById('newGroupModal');
|
||||||
|
if (modal) modal.style.display = 'none';
|
||||||
|
|
||||||
|
var existing = state.conversations.find(function (c) { return c.id === result.id; });
|
||||||
|
if (!existing) {
|
||||||
|
state.conversations.unshift(result);
|
||||||
|
} else {
|
||||||
|
Object.assign(existing, result);
|
||||||
|
}
|
||||||
|
ConversationList.renderList();
|
||||||
|
ConversationList.selectConversation(result.id);
|
||||||
|
} catch (e) {
|
||||||
|
alert('Nie udało się utworzyć grupy: ' + e.message);
|
||||||
|
} finally {
|
||||||
|
state._isCreating = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 11. SEARCH
|
// 11. SEARCH
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@ -2571,6 +2811,7 @@
|
|||||||
ConversationList.renderList();
|
ConversationList.renderList();
|
||||||
Composer.init();
|
Composer.init();
|
||||||
NewMessageModal.init();
|
NewMessageModal.init();
|
||||||
|
NewGroupModal.init();
|
||||||
Search.init();
|
Search.init();
|
||||||
Pins.init();
|
Pins.init();
|
||||||
initContextMenu();
|
initContextMenu();
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/quill.snow.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/quill.snow.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/conversations.css') }}?v=11">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/conversations.css') }}?v=12">
|
||||||
<script src="{{ url_for('static', filename='js/vendor/quill.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/vendor/quill.js') }}"></script>
|
||||||
<style>
|
<style>
|
||||||
footer { display: none !important; }
|
footer { display: none !important; }
|
||||||
@ -64,6 +64,15 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="btn-new-text">Nowa wiadomość</span>
|
<span class="btn-new-text">Nowa wiadomość</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn-new-conversation btn-new-group" id="newGroupBtn" title="Nowa grupa">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
||||||
|
<circle cx="9" cy="7" r="4"></circle>
|
||||||
|
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
|
||||||
|
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="btn-new-text">Nowa grupa</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="conversation-list" id="conversationList">
|
<div class="conversation-list" id="conversationList">
|
||||||
@ -208,6 +217,33 @@
|
|||||||
<button data-emoji="✅">✅</button>
|
<button data-emoji="✅">✅</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- New group modal -->
|
||||||
|
<div class="modal-overlay" id="newGroupModal" style="display:none">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Nowa grupa</h3>
|
||||||
|
<button class="modal-close" id="closeNewGroup">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="recipient-input">
|
||||||
|
<label>Nazwa grupy:</label>
|
||||||
|
<input type="text" id="groupNameInput" placeholder="np. Projekt XYZ, Zarząd..." style="margin-bottom:12px">
|
||||||
|
</div>
|
||||||
|
<div class="recipient-input">
|
||||||
|
<label>Członkowie:</label>
|
||||||
|
<input type="text" id="groupRecipientSearch" placeholder="Wpisz imię lub nazwisko...">
|
||||||
|
<div class="recipient-suggestions" id="groupRecipientSuggestions"></div>
|
||||||
|
<div class="selected-recipients" id="groupSelectedRecipients"></div>
|
||||||
|
</div>
|
||||||
|
<div id="groupMessageEditor"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn-secondary" id="cancelNewGroup">Anuluj</button>
|
||||||
|
<button class="btn-primary" id="sendNewGroup">Utwórz grupę</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- New message modal -->
|
<!-- New message modal -->
|
||||||
<div class="modal-overlay" id="newMessageModal" style="display:none">
|
<div class="modal-overlay" id="newMessageModal" style="display:none">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@ -256,7 +292,7 @@ window.__CSRF_TOKEN__ = '{{ csrf_token() }}';
|
|||||||
// Load conversations.js after data is set
|
// Load conversations.js after data is set
|
||||||
(function() {
|
(function() {
|
||||||
var s = document.createElement('script');
|
var s = document.createElement('script');
|
||||||
s.src = '{{ url_for("static", filename="js/conversations.js") }}?v=18';
|
s.src = '{{ url_for("static", filename="js/conversations.js") }}?v=19';
|
||||||
document.body.appendChild(s);
|
document.body.appendChild(s);
|
||||||
})();
|
})();
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user