diff --git a/static/css/conversations.css b/static/css/conversations.css index c93cdd6..13f084d 100644 --- a/static/css/conversations.css +++ b/static/css/conversations.css @@ -1664,3 +1664,153 @@ mark.search-highlight { border-radius: var(--conv-radius); } } + +/* ============================================================ + * GROUP SETTINGS BUTTON & MANAGEMENT PANEL + * ============================================================ */ + +.btn-group-settings { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border: none; + border-radius: var(--conv-radius); + background: transparent; + color: var(--conv-text-secondary); + cursor: pointer; + transition: background 0.15s, color 0.15s; +} +.btn-group-settings:hover { + background: var(--conv-surface-secondary); + color: var(--conv-primary); +} + +.group-panel { + background: var(--conv-surface); + border-bottom: 1px solid var(--conv-border); + max-height: 50vh; + overflow-y: auto; +} + +.group-panel-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + border-bottom: 1px solid var(--conv-border); +} +.group-panel-header h3 { + margin: 0; + font-size: 15px; + font-weight: 600; +} + +.group-panel-body { + padding: 12px 16px; +} + +.group-panel-section { + margin-bottom: 14px; +} +.group-panel-section:last-child { + margin-bottom: 0; +} +.group-panel-section label { + display: block; + font-size: 13px; + font-weight: 500; + color: var(--conv-text-secondary); + margin-bottom: 6px; +} + +.group-name-row { + display: flex; + gap: 8px; +} +.group-name-row input { + flex: 1; + padding: 7px 10px; + border: 1px solid var(--conv-border); + border-radius: var(--conv-radius); + font-size: 13px; + outline: none; +} +.group-name-row input:focus { + border-color: var(--conv-primary); +} + +.btn-sm { + padding: 6px 14px !important; + font-size: 12px !important; +} + +.group-members-list { + display: flex; + flex-direction: column; + gap: 2px; +} + +.group-member-item { + display: flex; + align-items: center; + gap: 10px; + padding: 6px 8px; + border-radius: var(--conv-radius); +} +.group-member-item:hover { + background: var(--conv-surface-secondary); +} + +.group-member-info { + flex: 1; + min-width: 0; +} +.group-member-name { + font-size: 13px; + font-weight: 500; +} +.group-member-role { + font-size: 11px; + color: var(--conv-text-muted); +} + +.group-member-actions { + display: flex; + gap: 4px; +} + +.btn-member-action { + padding: 3px 8px; + font-size: 11px; + border: 1px solid var(--conv-border); + border-radius: 4px; + background: transparent; + color: var(--conv-text-secondary); + cursor: pointer; + transition: all 0.15s; +} +.btn-member-action:hover { + background: var(--conv-surface-secondary); +} +.btn-member-action.danger { + color: #d32f2f; + border-color: #d32f2f; +} +.btn-member-action.danger:hover { + background: #fce4ec; +} + +#groupAddMemberSearch { + width: 100%; + padding: 7px 10px; + border: 1px solid var(--conv-border); + border-radius: var(--conv-radius); + font-size: 13px; + outline: none; + box-sizing: border-box; +} +#groupAddMemberSearch:focus { + border-color: var(--conv-primary); +} diff --git a/static/js/conversations.js b/static/js/conversations.js index 50d60d8..be59b48 100644 --- a/static/js/conversations.js +++ b/static/js/conversations.js @@ -289,6 +289,7 @@ var headerName = document.getElementById('headerName'); if (headerName) headerName.textContent = conv.display_name || conv.name || 'Bez nazwy'; ChatView.updateHeaderAvatar(conv); + GroupManager.updateSettingsButton(conv); } // Load conversation details + messages @@ -2441,6 +2442,280 @@ }, }; + // ============================================================ + // 10c. GROUP MANAGEMENT PANEL + // ============================================================ + + var GroupManager = { + _visible: false, + _debounceTimer: null, + + init: function () { + var settingsBtn = document.getElementById('groupSettingsBtn'); + var closeBtn = document.getElementById('closeGroupPanel'); + var saveNameBtn = document.getElementById('saveGroupName'); + var addSearch = document.getElementById('groupAddMemberSearch'); + + if (settingsBtn) { + settingsBtn.addEventListener('click', function () { + GroupManager.toggle(); + }); + } + if (closeBtn) { + closeBtn.addEventListener('click', function () { + GroupManager.hide(); + }); + } + if (saveNameBtn) { + saveNameBtn.addEventListener('click', function () { + GroupManager.saveName(); + }); + } + if (addSearch) { + addSearch.addEventListener('input', function () { + clearTimeout(GroupManager._debounceTimer); + GroupManager._debounceTimer = setTimeout(function () { + GroupManager.searchNewMember(addSearch.value); + }, 200); + }); + } + }, + + show: function () { + var panel = document.getElementById('groupPanel'); + if (!panel) return; + GroupManager._visible = true; + panel.style.display = 'block'; + GroupManager.loadDetails(); + }, + + hide: function () { + var panel = document.getElementById('groupPanel'); + if (!panel) return; + GroupManager._visible = false; + panel.style.display = 'none'; + }, + + toggle: function () { + if (GroupManager._visible) { + GroupManager.hide(); + } else { + GroupManager.show(); + } + }, + + updateSettingsButton: function (conv) { + var btn = document.getElementById('groupSettingsBtn'); + if (!btn) return; + btn.style.display = conv && conv.is_group ? '' : 'none'; + // Hide panel when switching conversations + GroupManager.hide(); + }, + + loadDetails: async function () { + var convId = state.currentConversation; + if (!convId) return; + + var details = state.conversationDetails[convId]; + if (!details) { + try { + details = await api('/api/conversations/' + convId); + state.conversationDetails[convId] = details; + } catch (e) { return; } + } + + // Name + var nameInput = document.getElementById('groupEditName'); + if (nameInput) nameInput.value = details.name || ''; + + // Owner check + var isOwner = details.members.some(function (m) { + return m.user_id === window.__CURRENT_USER__.id && m.role === 'owner'; + }); + + // Save name — show only for owner + var saveBtn = document.getElementById('saveGroupName'); + if (saveBtn) saveBtn.style.display = isOwner ? '' : 'none'; + if (nameInput) nameInput.readOnly = !isOwner; + + // Add member section — owner only + var addSection = document.getElementById('groupAddMemberSection'); + if (addSection) addSection.style.display = isOwner ? '' : 'none'; + + // Members list + var countEl = document.getElementById('groupMemberCount'); + if (countEl) countEl.textContent = details.members.length; + + var listEl = document.getElementById('groupMembersList'); + if (!listEl) return; + listEl.innerHTML = ''; + + details.members.forEach(function (m) { + var item = el('div', 'group-member-item'); + + var avatar = el('div', 'conv-avatar ' + avatarColor(m.name)); + avatar.style.width = '30px'; + avatar.style.height = '30px'; + avatar.style.minWidth = '30px'; + avatar.style.fontSize = '11px'; + avatar.textContent = initials(m.name); + + var info = el('div', 'group-member-info'); + var nameEl = el('div', 'group-member-name', m.name); + info.appendChild(nameEl); + if (m.role === 'owner') { + var roleEl = el('span', 'group-member-role', ' (właściciel)'); + nameEl.appendChild(roleEl); + } + if (m.company_name) { + var companyEl = el('div', 'group-member-role', m.company_name); + info.appendChild(companyEl); + } + + item.appendChild(avatar); + item.appendChild(info); + + // Actions (owner can remove others, anyone can see) + if (isOwner && m.user_id !== window.__CURRENT_USER__.id) { + var actions = el('div', 'group-member-actions'); + var removeBtn = el('button', 'btn-member-action danger', 'Usuń'); + removeBtn.addEventListener('click', function () { + GroupManager.removeMember(convId, m.user_id, m.name); + }); + actions.appendChild(removeBtn); + item.appendChild(actions); + } + + listEl.appendChild(item); + }); + }, + + saveName: async function () { + var convId = state.currentConversation; + var nameInput = document.getElementById('groupEditName'); + if (!convId || !nameInput) return; + + var newName = nameInput.value.trim(); + if (!newName) return; + + try { + await api('/api/conversations/' + convId, 'PATCH', { name: newName }); + // Update local state + var conv = state.conversations.find(function (c) { return c.id === convId; }); + if (conv) { + conv.name = newName; + conv.display_name = newName; + } + if (state.conversationDetails[convId]) { + state.conversationDetails[convId].name = newName; + } + ConversationList.renderList(); + var headerName = document.getElementById('headerName'); + if (headerName) headerName.textContent = newName; + } catch (e) { + alert('Nie udało się zmienić nazwy: ' + e.message); + } + }, + + removeMember: async function (convId, userId, userName) { + if (!confirm('Czy na pewno chcesz usunąć ' + userName + ' z grupy?')) return; + + try { + await api('/api/conversations/' + convId + '/members/' + userId, 'DELETE'); + // Refresh details + delete state.conversationDetails[convId]; + GroupManager.loadDetails(); + // Update subtitle + ChatView.loadConversationDetails(convId); + } catch (e) { + alert('Nie udało się usunąć członka: ' + e.message); + } + }, + + searchNewMember: async function (query) { + var suggestions = document.getElementById('groupAddMemberSuggestions'); + if (!suggestions) return; + suggestions.innerHTML = ''; + + query = (query || '').trim(); + if (query.length < 2) return; + + var convId = state.currentConversation; + var details = state.conversationDetails[convId]; + var existingIds = details ? details.members.map(function (m) { return m.user_id; }) : []; + + suggestions.innerHTML = '