f7support: точка непрочитанного по изменению карточки (fingerprint)
Если API не отдаёт has_unread или не обновляет его в списке, показываем зелёную точку при смене activity/updated_at или preview относительно последнего зафиксированного состояния. Учёт альтернативных полей hasUnread/unread/unread_count. После открытия чата — fetchTickets для актуального отпечатка.
This commit is contained in:
+57
-2
@@ -71,6 +71,8 @@
|
|||||||
wsReconnectTimer: null,
|
wsReconnectTimer: null,
|
||||||
ticketsPollTimer: null,
|
ticketsPollTimer: null,
|
||||||
pendingFile: null,
|
pendingFile: null,
|
||||||
|
/** @type {Record<string, string>} последний известный «отпечаток» карточки по номеру тикета (для точки без has_unread с API) */
|
||||||
|
ticketSeenFingerprint: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function clientIdentityHeaders() {
|
function clientIdentityHeaders() {
|
||||||
@@ -711,9 +713,55 @@
|
|||||||
window.setTimeout(run, 320);
|
window.setTimeout(run, 320);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ticketBoardFingerprint(t) {
|
||||||
|
const act = String(t.activity_at || t.updated_at || t.last_activity_at || t.created_at || "");
|
||||||
|
const prev = t.preview_text != null ? String(t.preview_text) : "";
|
||||||
|
return `${act}\u0001${prev}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Для открытого чата учитываем последнее сообщение, чтобы отпечаток не отставал от превью в списке. */
|
||||||
|
function effectiveBoardFingerprint(t) {
|
||||||
|
const base = ticketBoardFingerprint(t);
|
||||||
|
const tn = String(t.ticket_number);
|
||||||
|
if (state.chatModalOpen && state.currentTicket != null && String(state.currentTicket) === tn) {
|
||||||
|
const msgs = state.messages;
|
||||||
|
if (msgs && msgs.length) {
|
||||||
|
const last = msgs[msgs.length - 1];
|
||||||
|
return `${base}\u0002${last.id ?? ""}\u0003${last.created_at ?? ""}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBoardUnreadFingerprints() {
|
||||||
|
for (const t of state.tickets) {
|
||||||
|
const tn = String(t.ticket_number);
|
||||||
|
const fp = effectiveBoardFingerprint(t);
|
||||||
|
const seenKey = tn;
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(state.ticketSeenFingerprint, seenKey)) {
|
||||||
|
state.ticketSeenFingerprint[seenKey] = fp;
|
||||||
|
t._f7_board_unread = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (state.chatModalOpen && state.currentTicket != null && String(state.currentTicket) === tn) {
|
||||||
|
state.ticketSeenFingerprint[seenKey] = fp;
|
||||||
|
t._f7_board_unread = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
t._f7_board_unread = fp !== state.ticketSeenFingerprint[seenKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function ticketHasUnread(ticket) {
|
function ticketHasUnread(ticket) {
|
||||||
const v = ticket?.has_unread;
|
if (ticket && ticket._f7_board_unread) return true;
|
||||||
return v === true || v === 1 || v === "1" || v === "true";
|
const raw =
|
||||||
|
ticket?.has_unread ??
|
||||||
|
ticket?.hasUnread ??
|
||||||
|
ticket?.unread ??
|
||||||
|
ticket?.is_unread ??
|
||||||
|
ticket?.unread_count;
|
||||||
|
if (typeof raw === "number" && raw > 0) return true;
|
||||||
|
return raw === true || raw === 1 || raw === "1" || raw === "true" || raw === "yes";
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleTicketsBoardPolling() {
|
function scheduleTicketsBoardPolling() {
|
||||||
@@ -792,6 +840,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeChatModal() {
|
function closeChatModal() {
|
||||||
|
const closingTn = state.currentTicket;
|
||||||
|
if (closingTn != null) {
|
||||||
|
const row = state.tickets.find((x) => String(x.ticket_number) === String(closingTn));
|
||||||
|
if (row) state.ticketSeenFingerprint[String(closingTn)] = ticketBoardFingerprint(row);
|
||||||
|
}
|
||||||
disconnectTicketSocket();
|
disconnectTicketSocket();
|
||||||
revokeAttachmentUrls();
|
revokeAttachmentUrls();
|
||||||
state.chatModalOpen = false;
|
state.chatModalOpen = false;
|
||||||
@@ -816,6 +869,7 @@
|
|||||||
});
|
});
|
||||||
if (!response.ok) throw new Error("Не удалось загрузить список обращений");
|
if (!response.ok) throw new Error("Не удалось загрузить список обращений");
|
||||||
state.tickets = await response.json();
|
state.tickets = await response.json();
|
||||||
|
updateBoardUnreadFingerprints();
|
||||||
renderTickets();
|
renderTickets();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,6 +940,7 @@
|
|||||||
openChatModal(ticket);
|
openChatModal(ticket);
|
||||||
try {
|
try {
|
||||||
await fetchMessages();
|
await fetchMessages();
|
||||||
|
await fetchTickets();
|
||||||
const row = state.tickets.find((x) => x.ticket_number === ticketNumber);
|
const row = state.tickets.find((x) => x.ticket_number === ticketNumber);
|
||||||
if (row) row.has_unread = false;
|
if (row) row.has_unread = false;
|
||||||
renderTickets();
|
renderTickets();
|
||||||
|
|||||||
Reference in New Issue
Block a user