diff --git a/public/admin.css b/public/admin.css index bddfece..fc1ecb4 100644 --- a/public/admin.css +++ b/public/admin.css @@ -55,7 +55,7 @@ label { display: grid; gap: 6px; color: var(--muted); font-size: 13px; } border-right: 1px solid var(--line); background: var(--panel); display: grid; - grid-template-rows: auto auto minmax(0, 1fr); + grid-template-rows: auto auto auto minmax(0, 1fr); } .brand, .sitebar { display: flex; @@ -69,22 +69,40 @@ label { display: grid; gap: 6px; color: var(--muted); font-size: 13px; } .brand button, .sitebar button { background: #edf3ef; color: var(--ink); } .switch { display: flex; align-items: center; gap: 8px; } .switch input { width: auto; } +.list-tools { + padding: 10px 14px; + border-bottom: 1px solid var(--line); + background: #fbfdfb; +} +.list-tools label { color: var(--muted); } .conversation-list { overflow: auto; } .conversation-item { - width: 100%; - text-align: left; border: 0; border-bottom: 1px solid var(--line); background: white; color: var(--ink); border-radius: 0; - padding: 12px 14px; + padding: 10px; display: grid; - gap: 5px; + grid-template-columns: minmax(0, 1fr) auto; + gap: 8px; + align-items: start; } .conversation-item.active { background: #eaf6f1; } .conversation-item.fresh { animation: pulse 1.2s ease-in-out 4; } +.conversation-open { + min-width: 0; + text-align: left; + display: grid; + gap: 5px; + border: 0; + background: transparent; + color: var(--ink); + border-radius: 6px; + padding: 2px; +} +.conversation-open:hover { filter: none; background: #f3f7f4; } .row { display: flex; justify-content: space-between; gap: 10px; align-items: center; } .status { font-size: 12px; @@ -93,7 +111,59 @@ label { display: grid; gap: 6px; color: var(--muted); font-size: 13px; } border-radius: 999px; padding: 2px 8px; } +.photo-count { + flex: 0 0 auto; + color: var(--brand); + font-size: 12px; +} .snippet, .preview, .meta { color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.list-photo { + grid-column: 1 / -1; + width: 100%; + height: 74px; + padding: 0; + border: 1px solid var(--line); + border-radius: 8px; + overflow: hidden; + background: #edf3ef; +} +.list-photo img { + width: 100%; + height: 100%; + display: block; + object-fit: cover; +} +.conversation-actions { + grid-column: 1 / -1; + display: grid; + grid-template-columns: minmax(0, 1fr) 34px; + gap: 8px; +} +.status-select { + height: 34px; + padding: 5px 8px; + font-size: 12px; +} +.status-select.new { border-color: #2f9e44; background: #effaf2; } +.status-select.open { border-color: #2f80ed; background: #eef5ff; } +.status-select.resolved { border-color: #87908a; background: #f3f5f3; } +.status-select.spam { border-color: #d92d20; background: #fff1f0; } +.delete-chat { + width: 34px; + height: 34px; + padding: 0; + display: grid; + place-items: center; + background: #fff1f0; + color: #b42318; +} +.delete-chat svg { width: 18px; height: 18px; fill: currentColor; } +.empty-list { + margin: 0; + padding: 18px 14px; + color: var(--muted); + font-size: 13px; +} .conversation { display: grid; grid-template-rows: auto minmax(0, 1fr) auto auto; min-width: 0; } .conversation-header { diff --git a/public/admin.html b/public/admin.html index 569aac6..b9f921f 100644 --- a/public/admin.html +++ b/public/admin.html @@ -32,6 +32,18 @@ +
Nova konverzace se tady zvyrazni a pusti zvuk.
- diff --git a/public/admin.js b/public/admin.js index 3f29cf9..6ad5b6e 100644 --- a/public/admin.js +++ b/public/admin.js @@ -4,7 +4,8 @@ const state = { conversations: [], activeId: null, active: null, - events: null + events: null, + statusFilter: "active" }; const $ = (selector) => document.querySelector(selector); @@ -78,13 +79,30 @@ $("#replyForm textarea").addEventListener("input", debounce(() => { api(`/api/admin/conversations/${state.activeId}/typing`, { method: "POST" }).catch(() => {}); }, 600)); -$("#statusSelect").addEventListener("change", async () => { - if (!state.activeId) return; - await api(`/api/admin/conversations/${state.activeId}/status`, { - method: "PATCH", - body: { status: $("#statusSelect").value } - }); - await loadConversations(); +$("#conversationFilter").addEventListener("change", () => { + state.statusFilter = $("#conversationFilter").value; + renderConversations(); +}); + +$("#conversationList").addEventListener("click", async (event) => { + const photoButton = event.target.closest("[data-photo-url]"); + const openButton = event.target.closest("[data-open]"); + const deleteButton = event.target.closest("[data-delete]"); + if (photoButton) { + openPhotoPreview(photoButton.dataset.photoUrl, photoButton.dataset.photoName); + return; + } + if (deleteButton) { + await deleteConversation(deleteButton.dataset.delete); + return; + } + if (openButton) await openConversation(openButton.dataset.open); +}); + +$("#conversationList").addEventListener("change", async (event) => { + const statusSelect = event.target.closest("[data-status-for]"); + if (!statusSelect) return; + await updateConversationStatus(statusSelect.dataset.statusFor, statusSelect.value); }); $("#messages").addEventListener("click", (event) => { @@ -140,7 +158,11 @@ function connectEvents() { return; } await loadConversations(); - if (state.activeId) await openConversation(state.activeId); + if (data.type === "conversation:delete" && data.conversationId === state.activeId) { + clearActiveConversation(); + return; + } + if (state.activeId) await openConversation(state.activeId).catch(() => clearActiveConversation()); if (data.type === "conversation:new" || data.type === "message:new") notify(); }); } @@ -164,21 +186,29 @@ function renderSite() { } function renderConversations() { - $("#conversationList").innerHTML = state.conversations.map((item) => ` - - `).join(""); - for (const button of document.querySelectorAll(".conversation-item")) { - button.addEventListener("click", () => openConversation(button.dataset.id)); - } + const conversations = filteredConversations(); + $("#conversationList").innerHTML = conversations.length ? conversations.map((item) => ` +Zadny chat pro vybrany filtr.
`; } function renderActive() { const { conversation, messages } = state.active; - const statusSelect = $("#statusSelect"); $("#conversationHeader").innerHTML = `${escapeHtml(conversation.device.userAgent || "")}