Upload patient.js
Browse files- static/js/patient.js +19 -82
static/js/patient.js
CHANGED
|
@@ -34,6 +34,7 @@
|
|
| 34 |
const openHistoryBtn = document.getElementById("open-history-btn");
|
| 35 |
const historyBackBtn = document.getElementById("history-back-btn");
|
| 36 |
const historyFullList = document.getElementById("history-full-list");
|
|
|
|
| 37 |
const historySearchInput = document.getElementById("history-search");
|
| 38 |
const chatThread = document.getElementById("chat-thread");
|
| 39 |
const waitingCard = document.getElementById("waiting-card");
|
|
@@ -56,17 +57,7 @@
|
|
| 56 |
|
| 57 |
function showStep(stepNumber) {
|
| 58 |
document.querySelectorAll(".step").forEach(function (section) {
|
| 59 |
-
|
| 60 |
-
section.classList.toggle("active", isActive);
|
| 61 |
-
// Move focus to the first heading or focusable element in the
|
| 62 |
-
// newly shown step so keyboard users don't get stranded at
|
| 63 |
-
// wherever the trigger element was.
|
| 64 |
-
if (isActive) {
|
| 65 |
-
requestAnimationFrame(function () {
|
| 66 |
-
const target = section.querySelector("h2, h3, [tabindex='0'], button, a, input");
|
| 67 |
-
if (target) target.focus({ preventScroll: false });
|
| 68 |
-
});
|
| 69 |
-
}
|
| 70 |
});
|
| 71 |
}
|
| 72 |
|
|
@@ -420,8 +411,6 @@
|
|
| 420 |
|
| 421 |
// Past messages for this session.
|
| 422 |
let cachedHistoryItems = [];
|
| 423 |
-
let historyStatusFilter = "all"; // "all" | "waiting" | "answered"
|
| 424 |
-
let historyScrollY = 0; // remembered scroll offset for Back navigation
|
| 425 |
|
| 426 |
function buildHistoryRow(item, index) {
|
| 427 |
const row = document.createElement("div");
|
|
@@ -433,14 +422,9 @@
|
|
| 433 |
const badgeClass = answered ? "answered" : "waiting";
|
| 434 |
const badgeLabel = answered ? "Answered" : "Waiting for reply";
|
| 435 |
const timeLabel = window.formatRelativeTime ? window.formatRelativeTime(item.timestamp) : "";
|
| 436 |
-
// Descriptive aria-label so the row is announced meaningfully,
|
| 437 |
-
// e.g. "Answered, 2 hours ago: I have a fever..." rather than
|
| 438 |
-
// a string of badge text + visual metadata read in random order.
|
| 439 |
-
const ariaLabel = [badgeLabel, timeLabel, item.input_text].filter(Boolean).join(": ");
|
| 440 |
-
row.setAttribute("aria-label", ariaLabel);
|
| 441 |
row.innerHTML =
|
| 442 |
'<div class="result-label">#' + escapeHtml(item.id) +
|
| 443 |
-
' <span class="status-badge ' + badgeClass + '"
|
| 444 |
' <span class="timestamp">' + escapeHtml(timeLabel) + "</span></div>" +
|
| 445 |
'<div class="result-text">' + escapeHtml(item.input_text) + "</div>";
|
| 446 |
row.addEventListener("click", function () {
|
|
@@ -481,62 +465,34 @@
|
|
| 481 |
cachedHistoryItems = [];
|
| 482 |
}
|
| 483 |
|
| 484 |
-
|
| 485 |
-
// re-render it in place so status badges update immediately when a
|
| 486 |
-
// reply arrives via polling, without requiring the patient to close
|
| 487 |
-
// and reopen the view.
|
| 488 |
-
const historyStep = document.querySelector(".step[data-step='history']");
|
| 489 |
-
if (historyStep && historyStep.classList.contains("active")) {
|
| 490 |
-
renderFullHistoryList(historySearchInput.value);
|
| 491 |
-
} else {
|
| 492 |
-
renderFullHistoryList(historySearchInput.value);
|
| 493 |
-
}
|
| 494 |
}
|
| 495 |
|
| 496 |
// Dedicated history view: renders from the cache above (instant), so
|
| 497 |
// opening it never flashes empty while a fresh fetch is in flight.
|
| 498 |
-
// Applies both the active status filter and the text search query.
|
| 499 |
function renderFullHistoryList(filterText) {
|
| 500 |
const query = (filterText || "").trim().toLowerCase();
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
} else if (historyStatusFilter === "answered") {
|
| 507 |
-
filtered = filtered.filter(function (item) { return Boolean(item.translated_response); });
|
| 508 |
-
}
|
| 509 |
-
|
| 510 |
-
if (query) {
|
| 511 |
-
filtered = filtered.filter(function (item) {
|
| 512 |
-
return (item.input_text || "").toLowerCase().indexOf(query) !== -1;
|
| 513 |
-
});
|
| 514 |
-
}
|
| 515 |
|
| 516 |
historyFullList.innerHTML = "";
|
| 517 |
-
const emptyState = document.getElementById("history-empty-state");
|
| 518 |
-
const emptyTitle = document.getElementById("history-empty-title");
|
| 519 |
-
const emptyBody = document.getElementById("history-empty-body");
|
| 520 |
|
| 521 |
if (!cachedHistoryItems.length) {
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
emptyState.style.display = "block";
|
| 525 |
return;
|
| 526 |
}
|
| 527 |
|
| 528 |
if (!filtered.length) {
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
? "No messages match that search."
|
| 532 |
-
: historyStatusFilter === "waiting"
|
| 533 |
-
? "No consultations are still waiting for a reply."
|
| 534 |
-
: "No answered consultations yet.";
|
| 535 |
-
emptyState.style.display = "block";
|
| 536 |
return;
|
| 537 |
}
|
| 538 |
|
| 539 |
-
|
| 540 |
filtered.forEach(function (item, index) {
|
| 541 |
historyFullList.appendChild(buildHistoryRow(item, index));
|
| 542 |
});
|
|
@@ -590,17 +546,11 @@
|
|
| 590 |
|
| 591 |
openHistoryBtn.addEventListener("click", function () {
|
| 592 |
clearBanner();
|
| 593 |
-
historySearchInput.value = "";
|
| 594 |
showStep("history");
|
| 595 |
loadHistory();
|
| 596 |
-
// Restore scroll position from the last visit to this view
|
| 597 |
-
requestAnimationFrame(function () {
|
| 598 |
-
window.scrollTo(0, historyScrollY);
|
| 599 |
-
});
|
| 600 |
});
|
| 601 |
|
| 602 |
historyBackBtn.addEventListener("click", function () {
|
| 603 |
-
historyScrollY = window.scrollY;
|
| 604 |
showStep(1);
|
| 605 |
});
|
| 606 |
|
|
@@ -608,23 +558,6 @@
|
|
| 608 |
renderFullHistoryList(historySearchInput.value);
|
| 609 |
});
|
| 610 |
|
| 611 |
-
// Status filter chips — set initial aria-pressed state
|
| 612 |
-
document.querySelectorAll("#history-filter-chips .filter-chip").forEach(function (c) {
|
| 613 |
-
c.setAttribute("aria-pressed", c.classList.contains("active") ? "true" : "false");
|
| 614 |
-
});
|
| 615 |
-
|
| 616 |
-
document.getElementById("history-filter-chips").addEventListener("click", function (event) {
|
| 617 |
-
const chip = event.target.closest(".filter-chip");
|
| 618 |
-
if (!chip) return;
|
| 619 |
-
historyStatusFilter = chip.dataset.filter;
|
| 620 |
-
document.querySelectorAll("#history-filter-chips .filter-chip").forEach(function (c) {
|
| 621 |
-
const active = c === chip;
|
| 622 |
-
c.classList.toggle("active", active);
|
| 623 |
-
c.setAttribute("aria-pressed", active ? "true" : "false");
|
| 624 |
-
});
|
| 625 |
-
renderFullHistoryList(historySearchInput.value);
|
| 626 |
-
});
|
| 627 |
-
|
| 628 |
// Start a new conversation: ends the current session server-side and
|
| 629 |
// resets the page back to language selection.
|
| 630 |
newConversationBtn.addEventListener("click", async function () {
|
|
@@ -639,6 +572,7 @@
|
|
| 639 |
selectedLanguage = null;
|
| 640 |
recordedBlob = null;
|
| 641 |
currentInteractionId = null;
|
|
|
|
| 642 |
document.getElementById("patient-text").value = "";
|
| 643 |
document.getElementById("response-card").style.display = "none";
|
| 644 |
waitingCard.style.display = "block";
|
|
@@ -648,6 +582,7 @@
|
|
| 648 |
transcribingPanel.style.display = "none";
|
| 649 |
listeningPanel.style.display = "none";
|
| 650 |
inputPanel.style.display = "block";
|
|
|
|
| 651 |
setSubmitLabel("Send to provider");
|
| 652 |
languageGrid.querySelectorAll(".language-option").forEach(function (el) {
|
| 653 |
el.setAttribute("aria-pressed", "false");
|
|
@@ -655,7 +590,9 @@
|
|
| 655 |
toStep2Btn.disabled = true;
|
| 656 |
clearBanner();
|
| 657 |
showStep(1);
|
| 658 |
-
loadHistory()
|
|
|
|
|
|
|
| 659 |
showToast("New conversation started");
|
| 660 |
});
|
| 661 |
|
|
|
|
| 34 |
const openHistoryBtn = document.getElementById("open-history-btn");
|
| 35 |
const historyBackBtn = document.getElementById("history-back-btn");
|
| 36 |
const historyFullList = document.getElementById("history-full-list");
|
| 37 |
+
const historyEmptyText = document.getElementById("history-empty-text");
|
| 38 |
const historySearchInput = document.getElementById("history-search");
|
| 39 |
const chatThread = document.getElementById("chat-thread");
|
| 40 |
const waitingCard = document.getElementById("waiting-card");
|
|
|
|
| 57 |
|
| 58 |
function showStep(stepNumber) {
|
| 59 |
document.querySelectorAll(".step").forEach(function (section) {
|
| 60 |
+
section.classList.toggle("active", section.dataset.step === String(stepNumber));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
});
|
| 62 |
}
|
| 63 |
|
|
|
|
| 411 |
|
| 412 |
// Past messages for this session.
|
| 413 |
let cachedHistoryItems = [];
|
|
|
|
|
|
|
| 414 |
|
| 415 |
function buildHistoryRow(item, index) {
|
| 416 |
const row = document.createElement("div");
|
|
|
|
| 422 |
const badgeClass = answered ? "answered" : "waiting";
|
| 423 |
const badgeLabel = answered ? "Answered" : "Waiting for reply";
|
| 424 |
const timeLabel = window.formatRelativeTime ? window.formatRelativeTime(item.timestamp) : "";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
row.innerHTML =
|
| 426 |
'<div class="result-label">#' + escapeHtml(item.id) +
|
| 427 |
+
' <span class="status-badge ' + badgeClass + '">' + badgeLabel + "</span>" +
|
| 428 |
' <span class="timestamp">' + escapeHtml(timeLabel) + "</span></div>" +
|
| 429 |
'<div class="result-text">' + escapeHtml(item.input_text) + "</div>";
|
| 430 |
row.addEventListener("click", function () {
|
|
|
|
| 465 |
cachedHistoryItems = [];
|
| 466 |
}
|
| 467 |
|
| 468 |
+
renderFullHistoryList(historySearchInput.value);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 469 |
}
|
| 470 |
|
| 471 |
// Dedicated history view: renders from the cache above (instant), so
|
| 472 |
// opening it never flashes empty while a fresh fetch is in flight.
|
|
|
|
| 473 |
function renderFullHistoryList(filterText) {
|
| 474 |
const query = (filterText || "").trim().toLowerCase();
|
| 475 |
+
const filtered = query
|
| 476 |
+
? cachedHistoryItems.filter(function (item) {
|
| 477 |
+
return (item.input_text || "").toLowerCase().indexOf(query) !== -1;
|
| 478 |
+
})
|
| 479 |
+
: cachedHistoryItems;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 480 |
|
| 481 |
historyFullList.innerHTML = "";
|
|
|
|
|
|
|
|
|
|
| 482 |
|
| 483 |
if (!cachedHistoryItems.length) {
|
| 484 |
+
historyEmptyText.textContent = "You haven't sent any messages yet.";
|
| 485 |
+
historyEmptyText.style.display = "block";
|
|
|
|
| 486 |
return;
|
| 487 |
}
|
| 488 |
|
| 489 |
if (!filtered.length) {
|
| 490 |
+
historyEmptyText.textContent = "No messages match your search.";
|
| 491 |
+
historyEmptyText.style.display = "block";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 492 |
return;
|
| 493 |
}
|
| 494 |
|
| 495 |
+
historyEmptyText.style.display = "none";
|
| 496 |
filtered.forEach(function (item, index) {
|
| 497 |
historyFullList.appendChild(buildHistoryRow(item, index));
|
| 498 |
});
|
|
|
|
| 546 |
|
| 547 |
openHistoryBtn.addEventListener("click", function () {
|
| 548 |
clearBanner();
|
|
|
|
| 549 |
showStep("history");
|
| 550 |
loadHistory();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
});
|
| 552 |
|
| 553 |
historyBackBtn.addEventListener("click", function () {
|
|
|
|
| 554 |
showStep(1);
|
| 555 |
});
|
| 556 |
|
|
|
|
| 558 |
renderFullHistoryList(historySearchInput.value);
|
| 559 |
});
|
| 560 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 561 |
// Start a new conversation: ends the current session server-side and
|
| 562 |
// resets the page back to language selection.
|
| 563 |
newConversationBtn.addEventListener("click", async function () {
|
|
|
|
| 572 |
selectedLanguage = null;
|
| 573 |
recordedBlob = null;
|
| 574 |
currentInteractionId = null;
|
| 575 |
+
cachedHistoryItems = [];
|
| 576 |
document.getElementById("patient-text").value = "";
|
| 577 |
document.getElementById("response-card").style.display = "none";
|
| 578 |
waitingCard.style.display = "block";
|
|
|
|
| 582 |
transcribingPanel.style.display = "none";
|
| 583 |
listeningPanel.style.display = "none";
|
| 584 |
inputPanel.style.display = "block";
|
| 585 |
+
historyCard.style.display = "none";
|
| 586 |
setSubmitLabel("Send to provider");
|
| 587 |
languageGrid.querySelectorAll(".language-option").forEach(function (el) {
|
| 588 |
el.setAttribute("aria-pressed", "false");
|
|
|
|
| 590 |
toStep2Btn.disabled = true;
|
| 591 |
clearBanner();
|
| 592 |
showStep(1);
|
| 593 |
+
// Do not call loadHistory() here. end-session clears the server-side
|
| 594 |
+
// session so a /history fetch immediately after always returns empty.
|
| 595 |
+
// History reloads naturally after the next message is submitted.
|
| 596 |
showToast("New conversation started");
|
| 597 |
});
|
| 598 |
|