Spaces:
Sleeping
Sleeping
Update templates/doctor_dashboard.html
Browse files- templates/doctor_dashboard.html +535 -94
templates/doctor_dashboard.html
CHANGED
|
@@ -57,10 +57,18 @@
|
|
| 57 |
.btn-success {
|
| 58 |
background-color: var(--secondary-color);
|
| 59 |
border-color: var(--secondary-color);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
}
|
| 61 |
.btn-danger {
|
| 62 |
background-color: var(--danger-color);
|
| 63 |
border-color: var(--danger-color);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
}
|
| 65 |
.role-toggle {
|
| 66 |
display: flex;
|
|
@@ -105,6 +113,7 @@
|
|
| 105 |
box-shadow: 0 0.15rem 0.5rem 0 rgba(58, 59, 69, 0.05);
|
| 106 |
cursor: pointer;
|
| 107 |
transition: all 0.2s ease;
|
|
|
|
| 108 |
}
|
| 109 |
.patient-item:hover {
|
| 110 |
transform: translateY(-3px);
|
|
@@ -176,6 +185,27 @@
|
|
| 176 |
font-size: 1rem;
|
| 177 |
color: #333;
|
| 178 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
.patient-details-section {
|
| 180 |
display: none; /* Hidden by default */
|
| 181 |
}
|
|
@@ -212,7 +242,52 @@
|
|
| 212 |
#patient-feedback {
|
| 213 |
background-color: #e9ecef; /* Light grey */
|
| 214 |
border-color: #dee2e6; /* Light grey border */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
</style>
|
| 217 |
</head>
|
| 218 |
<body>
|
|
@@ -305,29 +380,42 @@
|
|
| 305 |
<strong>Last Updated:</strong> <span id="patient-timestamp"></span>
|
| 306 |
</div>
|
| 307 |
|
| 308 |
-
<!-- Download button now relies on patient ID -->
|
| 309 |
-
<button class="btn btn-primary" id="download-pdf">
|
| 310 |
-
<i class="fas fa-file-download me-2"></i>Download Care Plan PDF
|
| 311 |
-
</button>
|
| 312 |
-
|
| 313 |
<div class="care-plan-tabs mt-4">
|
| 314 |
<ul class="nav nav-tabs" id="carePlanTabs" role="tablist">
|
| 315 |
<li class="nav-item" role="presentation">
|
| 316 |
-
<
|
|
|
|
| 317 |
</li>
|
| 318 |
<li class="nav-item" role="presentation">
|
| 319 |
-
<button class="nav-link" id="original-plan-tab" data-bs-toggle="tab" data-bs-target="#original-plan" type="button" role="tab" aria-controls="original-plan" aria-selected="false">Original Care Plan</button>
|
| 320 |
</li>
|
| 321 |
</ul>
|
| 322 |
<div class="tab-content" id="carePlanTabsContent">
|
| 323 |
-
|
| 324 |
-
|
|
|
|
| 325 |
</div>
|
| 326 |
-
<
|
|
|
|
| 327 |
<div class="care-plan-content" id="original-plan-content"></div>
|
| 328 |
</div>
|
| 329 |
</div>
|
|
|
|
|
|
|
| 330 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
</div>
|
| 332 |
</div>
|
| 333 |
</div>
|
|
@@ -348,6 +436,10 @@
|
|
| 348 |
$('.role-toggle-btn[data-role="doctor"]').addClass('active');
|
| 349 |
$('.role-toggle-btn[data-role="patient"]').removeClass('active');
|
| 350 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
// Load all patients
|
| 352 |
function loadPatients() {
|
| 353 |
console.log("Loading patients...");
|
|
@@ -426,35 +518,66 @@
|
|
| 426 |
</div>
|
| 427 |
<span class="patient-status ${statusBadgeClass[patient.status] || 'status-unknown'}">${statusText[patient.status] || 'UNKNOWN'}</span>
|
| 428 |
</div>
|
|
|
|
|
|
|
|
|
|
| 429 |
</li>
|
| 430 |
`;
|
| 431 |
$('#patient-list').append(patientHtml);
|
| 432 |
});
|
| 433 |
|
| 434 |
-
// Add click event to patient items
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 441 |
|
| 442 |
-
// After loading the list, check if a patient ID is in the URL
|
| 443 |
const urlParams = new URLSearchParams(window.location.search);
|
| 444 |
-
const
|
| 445 |
-
|
| 446 |
-
|
|
|
|
| 447 |
// Check if this ID exists in the loaded list before trying to load details
|
| 448 |
-
if ($(`.patient-item[data-id="${
|
| 449 |
-
loadPatientDetails(
|
| 450 |
} else {
|
| 451 |
-
console.warn(`Patient ID "${
|
| 452 |
-
|
| 453 |
-
|
|
|
|
| 454 |
}
|
| 455 |
-
} else {
|
| 456 |
-
|
| 457 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
}
|
| 459 |
|
| 460 |
|
|
@@ -464,8 +587,7 @@
|
|
| 464 |
$('#patient-list').empty();
|
| 465 |
$('#emergency-alerts').empty();
|
| 466 |
$('#no-patients-found').show(); // Show no patients message
|
| 467 |
-
|
| 468 |
-
$('#patient-details-section').hide();
|
| 469 |
}
|
| 470 |
},
|
| 471 |
error: function(xhr, status, error) {
|
|
@@ -475,15 +597,30 @@
|
|
| 475 |
$('#patient-list-error').show().text('Error loading patients. Please try refreshing.');
|
| 476 |
$('#patient-count').text('0');
|
| 477 |
$('#emergency-alerts').empty();
|
| 478 |
-
|
| 479 |
-
$('#patient-details-section').hide();
|
| 480 |
}
|
| 481 |
});
|
| 482 |
}
|
| 483 |
|
| 484 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
function loadPatientDetails(patientId) {
|
| 486 |
console.log("Attempting to load details for patient ID:", patientId);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
// Highlight selected patient
|
| 488 |
$('.patient-item').removeClass('active');
|
| 489 |
$(`.patient-item[data-id="${patientId}"]`).addClass('active');
|
|
@@ -491,18 +628,16 @@
|
|
| 491 |
// Show patient details section, hide placeholder
|
| 492 |
$('#no-patient-selected').hide();
|
| 493 |
$('#patient-details-section').show();
|
|
|
|
| 494 |
|
| 495 |
-
// Optional: Show a loader in the details pane
|
| 496 |
-
|
| 497 |
|
| 498 |
$.ajax({
|
| 499 |
url: `/get_patient/${patientId}`,
|
| 500 |
type: 'GET',
|
| 501 |
success: function(response) {
|
| 502 |
-
|
| 503 |
-
// $('#patient-details-section .card-body .loader').remove();
|
| 504 |
-
// You would ideally restore the body content here
|
| 505 |
-
|
| 506 |
if (response.success) {
|
| 507 |
const patient = response.patient;
|
| 508 |
console.log("Successfully loaded patient details:", patient.name);
|
|
@@ -534,60 +669,335 @@
|
|
| 534 |
.attr('class', 'patient-status ' + (statusBadgeClass[patient.status] || 'status-unknown'));
|
| 535 |
|
| 536 |
// Update care plans content
|
| 537 |
-
|
|
|
|
|
|
|
| 538 |
$('#original-plan-content').text(patient.original_plan || 'No original care plan available (or could not be extracted from PDF).');
|
| 539 |
|
| 540 |
-
// Reset tab to updated plan and show content
|
| 541 |
$('#carePlanTabs button').removeClass('active').attr('aria-selected', 'false');
|
| 542 |
$('#updated-plan-tab').addClass('active').attr('aria-selected', 'true');
|
| 543 |
$('#carePlanTabsContent .tab-pane').removeClass('show active');
|
| 544 |
-
$('#updated-plan').addClass('show active');
|
| 545 |
-
|
|
|
|
| 546 |
|
| 547 |
-
// Set up PDF
|
| 548 |
$('#download-pdf').off('click').on('click', function() {
|
| 549 |
console.log("Download button clicked for patient ID:", patient.id);
|
| 550 |
window.location.href = `/download_pdf/${patient.id}`;
|
| 551 |
});
|
| 552 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 553 |
} else {
|
| 554 |
console.error('Error loading patient details:', response.error);
|
| 555 |
alert('Error: ' + response.error);
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
// Remove highlight from list item if details couldn't load
|
| 559 |
-
$(`.patient-item[data-id="${patientId}"]`).removeClass('active');
|
| 560 |
}
|
| 561 |
},
|
| 562 |
error: function(xhr, status, error) {
|
| 563 |
-
// Hide loader if shown
|
| 564 |
-
// $('#patient-details-section .card-body .loader').remove();
|
| 565 |
-
|
| 566 |
console.error("API Error loading patient details:", error, xhr.responseText);
|
| 567 |
-
alert('Error loading patient details. Patient may no longer exist.');
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 572 |
}
|
| 573 |
});
|
| 574 |
}
|
| 575 |
|
| 576 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 577 |
$('#patient-search').on('input', function() {
|
| 578 |
const searchTerm = $(this).val().toLowerCase();
|
| 579 |
-
let
|
| 580 |
$('.patient-item').each(function() {
|
| 581 |
const patientName = $(this).find('h6').text().toLowerCase();
|
| 582 |
const patientDetails = $(this).find('small').text().toLowerCase(); // Includes age/gender/timestamp
|
| 583 |
if (patientName.includes(searchTerm) || patientDetails.includes(searchTerm)) {
|
| 584 |
$(this).show();
|
| 585 |
-
|
| 586 |
} else {
|
| 587 |
$(this).hide();
|
| 588 |
}
|
| 589 |
});
|
| 590 |
-
|
|
|
|
| 591 |
$('#no-patients-found').hide();
|
| 592 |
} else {
|
| 593 |
$('#no-patients-found').show();
|
|
@@ -601,33 +1011,36 @@
|
|
| 601 |
// Periodically check for emergency notifications (and refresh list if found)
|
| 602 |
function checkEmergencyNotifications() {
|
| 603 |
console.log("Checking for emergency notifications...");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 604 |
$.ajax({
|
| 605 |
url: '/get_emergency_notifications',
|
| 606 |
type: 'GET',
|
| 607 |
success: function(response) {
|
| 608 |
if (response.success && response.notifications.length > 0) {
|
| 609 |
console.log(`Found ${response.notifications.length} emergency notifications.`);
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
|
|
|
|
|
|
|
|
|
| 613 |
// Update emergency alerts section
|
| 614 |
-
const alertCount = response.notifications.length;
|
| 615 |
let alertHtml = `
|
| 616 |
<div class="alert alert-danger mb-3">
|
| 617 |
<i class="fas fa-exclamation-triangle me-2"></i>
|
| 618 |
-
<strong>${
|
| 619 |
</div>
|
| 620 |
`;
|
| 621 |
$('#emergency-alerts').html(alertHtml);
|
| 622 |
// Reload the patient list to update sorting and potentially highlight new emergencies
|
| 623 |
-
const currentSelectedId = $('#patient-details-section').is(':visible') ? $('.patient-item.active').data('id') : null;
|
| 624 |
console.log("Reloading patient list due to new emergency alerts...");
|
| 625 |
loadPatients(); // This will re-render the list and handle selection from URL/previously selected ID
|
| 626 |
-
} else {
|
| 627 |
-
// If the count is the same, just ensure the alert is visible if needed
|
| 628 |
-
if (currentAlerts > 0) {
|
| 629 |
-
$('#emergency-alerts').show();
|
| 630 |
-
}
|
| 631 |
}
|
| 632 |
|
| 633 |
} else {
|
|
@@ -645,7 +1058,6 @@
|
|
| 645 |
}
|
| 646 |
|
| 647 |
// Check for emergency notifications periodically (e.g., every 30 seconds)
|
| 648 |
-
// Note: For a production system with many users, websockets would be more efficient.
|
| 649 |
setInterval(checkEmergencyNotifications, 30000); // 30 seconds
|
| 650 |
|
| 651 |
// Handle browser back/forward button for URL patient ID
|
|
@@ -653,37 +1065,66 @@
|
|
| 653 |
const urlParams = new URLSearchParams(window.location.search);
|
| 654 |
const patientIdFromUrl = urlParams.get('patient_id');
|
| 655 |
|
| 656 |
-
// Get the ID of the currently displayed
|
| 657 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 658 |
|
| 659 |
-
|
| 660 |
-
//
|
| 661 |
-
console.log(`URL changed to patient ID: ${patientIdFromUrl}. Loading details.`);
|
| 662 |
-
// Check if this ID exists in the currently rendered list before calling API
|
| 663 |
if ($(`.patient-item[data-id="${patientIdFromUrl}"]`).length > 0) {
|
| 664 |
loadPatientDetails(patientIdFromUrl);
|
| 665 |
} else {
|
| 666 |
console.warn(`Patient ID "${patientIdFromUrl}" from URL not found in current list.`);
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
$('#no-patient-selected').show();
|
| 670 |
}
|
| 671 |
-
} else if (!patientIdFromUrl &&
|
| 672 |
// If URL has no ID but a patient is currently displayed, hide details
|
| 673 |
console.log("URL changed, no patient ID. Hiding details.");
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 680 |
} else {
|
| 681 |
-
// If neither URL nor displayed patient has an ID (e.g., went back to dashboard root)
|
| 682 |
console.log("URL changed, no patient selected.");
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
$('#no-patient-selected').show();
|
| 686 |
-
// Optionally refresh list if needed, but not strictly necessary here
|
| 687 |
}
|
| 688 |
};
|
| 689 |
});
|
|
|
|
| 57 |
.btn-success {
|
| 58 |
background-color: var(--secondary-color);
|
| 59 |
border-color: var(--secondary-color);
|
| 60 |
+
}
|
| 61 |
+
.btn-success:hover {
|
| 62 |
+
background-color: #15a46d; /* Darker green */
|
| 63 |
+
border-color: #149a68;
|
| 64 |
}
|
| 65 |
.btn-danger {
|
| 66 |
background-color: var(--danger-color);
|
| 67 |
border-color: var(--danger-color);
|
| 68 |
+
}
|
| 69 |
+
.btn-danger:hover {
|
| 70 |
+
background-color: #c82333; /* Darker red */
|
| 71 |
+
border-color: #bd2130;
|
| 72 |
}
|
| 73 |
.role-toggle {
|
| 74 |
display: flex;
|
|
|
|
| 113 |
box-shadow: 0 0.15rem 0.5rem 0 rgba(58, 59, 69, 0.05);
|
| 114 |
cursor: pointer;
|
| 115 |
transition: all 0.2s ease;
|
| 116 |
+
position: relative; /* Needed for absolute positioning of delete */
|
| 117 |
}
|
| 118 |
.patient-item:hover {
|
| 119 |
transform: translateY(-3px);
|
|
|
|
| 185 |
font-size: 1rem;
|
| 186 |
color: #333;
|
| 187 |
}
|
| 188 |
+
|
| 189 |
+
.care-plan-editable-textarea {
|
| 190 |
+
background-color: white; /* Make textarea background white */
|
| 191 |
+
border-radius: 0 0 0.35rem 0.35rem;
|
| 192 |
+
border: 1px solid var(--border-color);
|
| 193 |
+
border-top: none;
|
| 194 |
+
padding: 1.5rem;
|
| 195 |
+
height: 400px;
|
| 196 |
+
overflow-y: auto;
|
| 197 |
+
font-size: 1rem;
|
| 198 |
+
color: #333;
|
| 199 |
+
width: 100%;
|
| 200 |
+
display: none; /* Hidden by default */
|
| 201 |
+
resize: vertical; /* Allow vertical resize */
|
| 202 |
+
}
|
| 203 |
+
.care-plan-editable-textarea:read-only {
|
| 204 |
+
background-color: var(--light-bg); /* Revert background when read-only */
|
| 205 |
+
cursor: default; /* Change cursor when read-only */
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
|
| 209 |
.patient-details-section {
|
| 210 |
display: none; /* Hidden by default */
|
| 211 |
}
|
|
|
|
| 242 |
#patient-feedback {
|
| 243 |
background-color: #e9ecef; /* Light grey */
|
| 244 |
border-color: #dee2e6; /* Light grey border */
|
| 245 |
+
white-space: pre-line; /* Preserve line breaks */
|
| 246 |
+
word-wrap: break-word;
|
| 247 |
+
}
|
| 248 |
+
/* Delete button styling */
|
| 249 |
+
.delete-patient-btn {
|
| 250 |
+
position: absolute;
|
| 251 |
+
top: 0.5rem;
|
| 252 |
+
right: 0.5rem;
|
| 253 |
+
background: none;
|
| 254 |
+
border: none;
|
| 255 |
+
color: var(--dark-color); /* Default color */
|
| 256 |
+
cursor: pointer;
|
| 257 |
+
font-size: 1.1rem;
|
| 258 |
+
padding: 0.3rem; /* Make clickable area larger */
|
| 259 |
+
line-height: 1;
|
| 260 |
+
z-index: 10; /* Ensure it's above other content */
|
| 261 |
+
opacity: 0; /* Hide by default */
|
| 262 |
+
transition: color 0.2s ease-in-out, opacity 0.2s ease-in-out;
|
| 263 |
+
}
|
| 264 |
+
.patient-item:hover .delete-patient-btn {
|
| 265 |
+
opacity: 1; /* Show on hover */
|
| 266 |
}
|
| 267 |
+
.delete-patient-btn:hover {
|
| 268 |
+
color: var(--danger-color); /* Red on hover */
|
| 269 |
+
}
|
| 270 |
+
/* Ensure patient item content doesn't overlap delete button area */
|
| 271 |
+
.patient-item > div:first-child {
|
| 272 |
+
margin-right: 2rem; /* Add margin to the content to make space for the button */
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
/* Styling for buttons row below tabs */
|
| 276 |
+
.plan-actions {
|
| 277 |
+
margin-top: 1rem;
|
| 278 |
+
display: flex;
|
| 279 |
+
justify-content: flex-start; /* Align buttons to the left */
|
| 280 |
+
gap: 10px; /* Space between buttons */
|
| 281 |
+
}
|
| 282 |
+
.plan-actions .btn {
|
| 283 |
+
min-width: 120px; /* Give buttons a minimum width */
|
| 284 |
+
}
|
| 285 |
+
.alert-dismissible .btn-close {
|
| 286 |
+
padding: 0.8rem 1.25rem; /* Increase padding to make close button easier to hit */
|
| 287 |
+
}
|
| 288 |
+
.alert-container {
|
| 289 |
+
margin-top: 1rem;
|
| 290 |
+
}
|
| 291 |
</style>
|
| 292 |
</head>
|
| 293 |
<body>
|
|
|
|
| 380 |
<strong>Last Updated:</strong> <span id="patient-timestamp"></span>
|
| 381 |
</div>
|
| 382 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
<div class="care-plan-tabs mt-4">
|
| 384 |
<ul class="nav nav-tabs" id="carePlanTabs" role="tablist">
|
| 385 |
<li class="nav-item" role="presentation">
|
| 386 |
+
<!-- We will hide the static updated-plan-content and show a textarea below -->
|
| 387 |
+
<button class="nav-link active" id="updated-plan-tab" data-bs-toggle="tab" data-bs-target="#updated-plan-tab-pane" type="button" role="tab" aria-controls="updated-plan-tab-pane" aria-selected="true">Updated Care Plan</button>
|
| 388 |
</li>
|
| 389 |
<li class="nav-item" role="presentation">
|
| 390 |
+
<button class="nav-link" id="original-plan-tab" data-bs-toggle="tab" data-bs-target="#original-plan-tab-pane" type="button" role="tab" aria-controls="original-plan-tab-pane" aria-selected="false">Original Care Plan</button>
|
| 391 |
</li>
|
| 392 |
</ul>
|
| 393 |
<div class="tab-content" id="carePlanTabsContent">
|
| 394 |
+
<!-- Placeholder pane for the updated plan (will be hidden) -->
|
| 395 |
+
<div class="tab-pane fade show active" id="updated-plan-tab-pane" role="tabpanel" aria-labelledby="updated-plan-tab">
|
| 396 |
+
<div class="care-plan-content" id="updated-plan-static-content"></div>
|
| 397 |
</div>
|
| 398 |
+
<!-- Pane for the original plan -->
|
| 399 |
+
<div class="tab-pane fade" id="original-plan-tab-pane" role="tabpanel" aria-labelledby="original-plan-tab">
|
| 400 |
<div class="care-plan-content" id="original-plan-content"></div>
|
| 401 |
</div>
|
| 402 |
</div>
|
| 403 |
+
<!-- Editable Textarea for Updated Plan -->
|
| 404 |
+
<textarea id="updated-plan-editable-content" class="care-plan-editable-textarea" rows="15" readonly></textarea>
|
| 405 |
</div>
|
| 406 |
+
|
| 407 |
+
<div class="plan-actions">
|
| 408 |
+
<button class="btn btn-outline-primary" id="edit-plan-btn"><i class="fas fa-edit me-2"></i>Edit Plan</button>
|
| 409 |
+
<button class="btn btn-success" id="save-plan-btn" style="display: none;"><i class="fas fa-save me-2"></i>Save Plan</button>
|
| 410 |
+
<button class="btn btn-primary" id="download-pdf"><i class="fas fa-file-download me-2"></i>Download PDF</button>
|
| 411 |
+
<button class="btn btn-success" id="resend-whatsapp-btn"><i class="fab fa-whatsapp me-2"></i>Send WhatsApp</button>
|
| 412 |
+
</div>
|
| 413 |
+
|
| 414 |
+
<div id="plan-actions-alerts" class="alert-container">
|
| 415 |
+
<!-- Alerts for save/resend will appear here -->
|
| 416 |
+
</div>
|
| 417 |
+
|
| 418 |
+
|
| 419 |
</div>
|
| 420 |
</div>
|
| 421 |
</div>
|
|
|
|
| 436 |
$('.role-toggle-btn[data-role="doctor"]').addClass('active');
|
| 437 |
$('.role-toggle-btn[data-role="patient"]').removeClass('active');
|
| 438 |
|
| 439 |
+
// Placeholder for storing selected patient ID
|
| 440 |
+
let currentlySelectedPatientId = null;
|
| 441 |
+
let isEditing = false; // State variable for edit mode
|
| 442 |
+
|
| 443 |
// Load all patients
|
| 444 |
function loadPatients() {
|
| 445 |
console.log("Loading patients...");
|
|
|
|
| 518 |
</div>
|
| 519 |
<span class="patient-status ${statusBadgeClass[patient.status] || 'status-unknown'}">${statusText[patient.status] || 'UNKNOWN'}</span>
|
| 520 |
</div>
|
| 521 |
+
<button class="delete-patient-btn" data-id="${patient.id}" title="Delete Patient">
|
| 522 |
+
<i class="fas fa-trash-alt"></i>
|
| 523 |
+
</button>
|
| 524 |
</li>
|
| 525 |
`;
|
| 526 |
$('#patient-list').append(patientHtml);
|
| 527 |
});
|
| 528 |
|
| 529 |
+
// Add click event to patient items (excluding delete button clicks)
|
| 530 |
+
$('#patient-list').on('click', '.patient-item', function(event) {
|
| 531 |
+
// Check if the click target is the delete button or its icon
|
| 532 |
+
if (!$(event.target).closest('.delete-patient-btn').length) {
|
| 533 |
+
const patientId = $(this).data('id');
|
| 534 |
+
// Only load details if not already viewing this patient AND not in edit mode
|
| 535 |
+
if (currentlySelectedPatientId !== patientId && !isEditing) {
|
| 536 |
+
// Update URL without full page reload
|
| 537 |
+
history.pushState({ patient_id: patientId }, '', `/doctor_dashboard?patient_id=${patientId}`);
|
| 538 |
+
loadPatientDetails(patientId);
|
| 539 |
+
} else if (isEditing) {
|
| 540 |
+
// Prompt user if they try to switch while editing
|
| 541 |
+
if (confirm("You have unsaved changes. Discard changes and load new patient details?")) {
|
| 542 |
+
isEditing = false; // Reset state
|
| 543 |
+
// Update URL and load
|
| 544 |
+
history.pushState({ patient_id: patientId }, '', `/doctor_dashboard?patient_id=${patientId}`);
|
| 545 |
+
loadPatientDetails(patientId);
|
| 546 |
+
}
|
| 547 |
+
}
|
| 548 |
+
}
|
| 549 |
+
});
|
| 550 |
+
|
| 551 |
|
| 552 |
+
// After loading the list, check if a patient ID is in the URL or if a patient was previously selected
|
| 553 |
const urlParams = new URLSearchParams(window.location.search);
|
| 554 |
+
const patientIdFromUrl = urlParams.get('patient_id');
|
| 555 |
+
|
| 556 |
+
if (patientIdFromUrl) {
|
| 557 |
+
console.log("Checking for patient from URL:", patientIdFromUrl);
|
| 558 |
// Check if this ID exists in the loaded list before trying to load details
|
| 559 |
+
if ($(`.patient-item[data-id="${patientIdFromUrl}"]`).length > 0) {
|
| 560 |
+
loadPatientDetails(patientIdFromUrl);
|
| 561 |
} else {
|
| 562 |
+
console.warn(`Patient ID "${patientIdFromUrl}" from URL not found in loaded list.`);
|
| 563 |
+
// Clear URL param if patient not found
|
| 564 |
+
history.replaceState({}, '', '/doctor_dashboard');
|
| 565 |
+
resetDetailsPane(); // Use reset function
|
| 566 |
}
|
| 567 |
+
} else if (currentlySelectedPatientId) {
|
| 568 |
+
// If no ID in URL, but we had one selected before refresh/reload, try to load it
|
| 569 |
+
console.log("Checking for previously selected patient:", currentlySelectedPatientId);
|
| 570 |
+
if ($(`.patient-item[data-id="${currentlySelectedPatientId}"]`).length > 0) {
|
| 571 |
+
loadPatientDetails(currentlySelectedPatientId);
|
| 572 |
+
} else {
|
| 573 |
+
console.warn(`Previously selected patient ID "${currentlySelectedPatientId}" not found in loaded list.`);
|
| 574 |
+
history.replaceState({}, '', '/doctor_dashboard');
|
| 575 |
+
resetDetailsPane(); // Use reset function
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
}
|
| 579 |
+
else {
|
| 580 |
+
resetDetailsPane(); // Use reset function
|
| 581 |
}
|
| 582 |
|
| 583 |
|
|
|
|
| 587 |
$('#patient-list').empty();
|
| 588 |
$('#emergency-alerts').empty();
|
| 589 |
$('#no-patients-found').show(); // Show no patients message
|
| 590 |
+
resetDetailsPane(); // Use reset function
|
|
|
|
| 591 |
}
|
| 592 |
},
|
| 593 |
error: function(xhr, status, error) {
|
|
|
|
| 597 |
$('#patient-list-error').show().text('Error loading patients. Please try refreshing.');
|
| 598 |
$('#patient-count').text('0');
|
| 599 |
$('#emergency-alerts').empty();
|
| 600 |
+
resetDetailsPane(); // Use reset function on API error
|
|
|
|
| 601 |
}
|
| 602 |
});
|
| 603 |
}
|
| 604 |
|
| 605 |
+
// Function to reset the patient details pane to the placeholder state
|
| 606 |
+
function resetDetailsPane() {
|
| 607 |
+
$('.patient-item').removeClass('active'); // Remove highlight from all items
|
| 608 |
+
$('#patient-details-section').hide(); // Hide details section
|
| 609 |
+
$('#no-patient-selected').show(); // Show placeholder message
|
| 610 |
+
currentlySelectedPatientId = null; // Reset selected ID
|
| 611 |
+
isEditing = false; // Ensure edit mode is off
|
| 612 |
+
toggleEditMode(false); // Reset button states etc.
|
| 613 |
+
$('#plan-actions-alerts').empty(); // Clear any alerts
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
|
| 617 |
+
// Load patient details and populate the pane
|
| 618 |
function loadPatientDetails(patientId) {
|
| 619 |
console.log("Attempting to load details for patient ID:", patientId);
|
| 620 |
+
currentlySelectedPatientId = patientId; // Store the ID
|
| 621 |
+
isEditing = false; // Ensure edit mode is off when loading new details
|
| 622 |
+
toggleEditMode(false); // Reset button states
|
| 623 |
+
|
| 624 |
// Highlight selected patient
|
| 625 |
$('.patient-item').removeClass('active');
|
| 626 |
$(`.patient-item[data-id="${patientId}"]`).addClass('active');
|
|
|
|
| 628 |
// Show patient details section, hide placeholder
|
| 629 |
$('#no-patient-selected').hide();
|
| 630 |
$('#patient-details-section').show();
|
| 631 |
+
$('#plan-actions-alerts').empty(); // Clear any alerts from previous patient
|
| 632 |
|
| 633 |
+
// Optional: Show a loader in the details pane while fetching
|
| 634 |
+
// $('#patient-details-section .card-body').html('<div class="loader"></div>');
|
| 635 |
|
| 636 |
$.ajax({
|
| 637 |
url: `/get_patient/${patientId}`,
|
| 638 |
type: 'GET',
|
| 639 |
success: function(response) {
|
| 640 |
+
// Hide loader if shown
|
|
|
|
|
|
|
|
|
|
| 641 |
if (response.success) {
|
| 642 |
const patient = response.patient;
|
| 643 |
console.log("Successfully loaded patient details:", patient.name);
|
|
|
|
| 669 |
.attr('class', 'patient-status ' + (statusBadgeClass[patient.status] || 'status-unknown'));
|
| 670 |
|
| 671 |
// Update care plans content
|
| 672 |
+
// Populate both the static div and the editable textarea
|
| 673 |
+
$('#updated-plan-static-content').text(patient.updated_plan || 'No updated care plan available.');
|
| 674 |
+
$('#updated-plan-editable-content').val(patient.updated_plan || ''); // Use val() for textarea
|
| 675 |
$('#original-plan-content').text(patient.original_plan || 'No original care plan available (or could not be extracted from PDF).');
|
| 676 |
|
| 677 |
+
// Reset tab to updated plan and show static content first
|
| 678 |
$('#carePlanTabs button').removeClass('active').attr('aria-selected', 'false');
|
| 679 |
$('#updated-plan-tab').addClass('active').attr('aria-selected', 'true');
|
| 680 |
$('#carePlanTabsContent .tab-pane').removeClass('show active');
|
| 681 |
+
$('#updated-plan-tab-pane').addClass('show active'); // Show static content pane
|
| 682 |
+
$('#updated-plan-static-content').show(); // Ensure static div is visible
|
| 683 |
+
$('#updated-plan-editable-content').hide().prop('readonly', true); // Ensure textarea is hidden and read-only
|
| 684 |
|
| 685 |
+
// Set up Download PDF button
|
| 686 |
$('#download-pdf').off('click').on('click', function() {
|
| 687 |
console.log("Download button clicked for patient ID:", patient.id);
|
| 688 |
window.location.href = `/download_pdf/${patient.id}`;
|
| 689 |
});
|
| 690 |
|
| 691 |
+
// Set up Send WhatsApp button
|
| 692 |
+
$('#resend-whatsapp-btn').off('click').on('click', function() {
|
| 693 |
+
console.log("Send WhatsApp button clicked for patient ID:", patient.id);
|
| 694 |
+
sendWhatsApp(patient.id);
|
| 695 |
+
});
|
| 696 |
+
|
| 697 |
+
|
| 698 |
} else {
|
| 699 |
console.error('Error loading patient details:', response.error);
|
| 700 |
alert('Error: ' + response.error);
|
| 701 |
+
resetDetailsPane(); // Reset on error
|
| 702 |
+
history.replaceState({}, '', '/doctor_dashboard'); // Clear URL param
|
|
|
|
|
|
|
| 703 |
}
|
| 704 |
},
|
| 705 |
error: function(xhr, status, error) {
|
|
|
|
|
|
|
|
|
|
| 706 |
console.error("API Error loading patient details:", error, xhr.responseText);
|
| 707 |
+
alert('Error loading patient details. Patient may no longer exist.');
|
| 708 |
+
resetDetailsPane(); // Reset on API error
|
| 709 |
+
history.replaceState({}, '', '/doctor_dashboard'); // Clear URL param
|
| 710 |
+
}
|
| 711 |
+
});
|
| 712 |
+
}
|
| 713 |
+
|
| 714 |
+
// Function to toggle between view and edit mode for the updated plan
|
| 715 |
+
function toggleEditMode(enable) {
|
| 716 |
+
isEditing = enable;
|
| 717 |
+
if (enable) {
|
| 718 |
+
// Switch to edit mode
|
| 719 |
+
$('#updated-plan-static-content').hide(); // Hide static div
|
| 720 |
+
$('#updated-plan-tab-pane').removeClass('show active'); // Hide the static pane
|
| 721 |
+
$('#updated-plan-editable-content').show().prop('readonly', false).focus(); // Show & enable textarea, focus it
|
| 722 |
+
$('#edit-plan-btn').hide(); // Hide Edit button
|
| 723 |
+
$('#save-plan-btn').show().prop('disabled', false); // Show & enable Save button
|
| 724 |
+
// Disable other actions while editing
|
| 725 |
+
$('#download-pdf').prop('disabled', true);
|
| 726 |
+
$('#resend-whatsapp-btn').prop('disabled', true);
|
| 727 |
+
$('#carePlanTabs button:not(#updated-plan-tab)').prop('disabled', true); // Disable other tabs
|
| 728 |
+
// Show a warning if needed
|
| 729 |
+
// displayAlert('warning', '<i class="fas fa-pencil-alt me-2"></i> You are in edit mode. Remember to save your changes.', 5000);
|
| 730 |
+
|
| 731 |
+
} else {
|
| 732 |
+
// Switch to view mode (or after saving/cancelling)
|
| 733 |
+
$('#updated-plan-static-content').show(); // Show static div
|
| 734 |
+
// Make sure the Updated Plan tab is active and its pane is shown
|
| 735 |
+
$('#carePlanTabs button').removeClass('active').attr('aria-selected', 'false').prop('disabled', false); // Enable all tabs
|
| 736 |
+
$('#updated-plan-tab').addClass('active').attr('aria-selected', 'true');
|
| 737 |
+
$('#carePlanTabsContent .tab-pane').removeClass('show active');
|
| 738 |
+
$('#updated-plan-tab-pane').addClass('show active');
|
| 739 |
+
|
| 740 |
+
$('#updated-plan-editable-content').hide().prop('readonly', true); // Hide & disable textarea
|
| 741 |
+
$('#edit-plan-btn').show().prop('disabled', false); // Show & enable Edit button
|
| 742 |
+
$('#save-plan-btn').hide(); // Hide Save button
|
| 743 |
+
// Enable other actions
|
| 744 |
+
$('#download-pdf').prop('disabled', false);
|
| 745 |
+
$('#resend-whatsapp-btn').prop('disabled', false);
|
| 746 |
+
$('#carePlanTabs button').prop('disabled', false); // Re-enable all tabs
|
| 747 |
+
|
| 748 |
+
}
|
| 749 |
+
}
|
| 750 |
+
|
| 751 |
+
// Handle Edit Plan button click
|
| 752 |
+
$('#edit-plan-btn').on('click', function() {
|
| 753 |
+
if (currentlySelectedPatientId) {
|
| 754 |
+
toggleEditMode(true);
|
| 755 |
+
}
|
| 756 |
+
});
|
| 757 |
+
|
| 758 |
+
// Handle Save Plan button click
|
| 759 |
+
$('#save-plan-btn').on('click', function() {
|
| 760 |
+
if (currentlySelectedPatientId) {
|
| 761 |
+
const editedPlanText = $('#updated-plan-editable-content').val();
|
| 762 |
+
if (!editedPlanText.trim()) {
|
| 763 |
+
displayAlert('danger', 'Care plan cannot be empty.', 5000);
|
| 764 |
+
return; // Stop save process if empty
|
| 765 |
+
}
|
| 766 |
+
saveCarePlan(currentlySelectedPatientId, editedPlanText);
|
| 767 |
+
}
|
| 768 |
+
});
|
| 769 |
+
|
| 770 |
+
// Function to save the edited care plan
|
| 771 |
+
function saveCarePlan(patientId, updatedPlanText) {
|
| 772 |
+
console.log(`Saving care plan for patient ID: ${patientId}`);
|
| 773 |
+
// Disable buttons and show loader during save
|
| 774 |
+
$('#save-plan-btn').prop('disabled', true).find('i').removeClass().addClass('fas fa-spinner fa-spin me-2');
|
| 775 |
+
$('#edit-plan-btn').prop('disabled', true); // Also disable edit button during save
|
| 776 |
+
$('#download-pdf').prop('disabled', true);
|
| 777 |
+
$('#resend-whatsapp-btn').prop('disabled', true);
|
| 778 |
+
$('#carePlanTabs button').prop('disabled', true); // Disable tabs
|
| 779 |
+
|
| 780 |
+
|
| 781 |
+
$.ajax({
|
| 782 |
+
url: `/save_care_plan/${patientId}`,
|
| 783 |
+
type: 'PUT',
|
| 784 |
+
contentType: 'application/json',
|
| 785 |
+
data: JSON.stringify({ updated_plan: updatedPlanText }),
|
| 786 |
+
success: function(response) {
|
| 787 |
+
console.log("Save successful:", response.message);
|
| 788 |
+
displayAlert('success', '<i class="fas fa-check-circle me-2"></i> Care plan saved successfully!', 5000);
|
| 789 |
+
|
| 790 |
+
// Update the static content area with the saved text
|
| 791 |
+
$('#updated-plan-static-content').text(updatedPlanText);
|
| 792 |
+
// Update the timestamp display
|
| 793 |
+
$('#patient-timestamp').text(response.new_timestamp ? new Date(response.new_timestamp).toLocaleString() : 'N/A');
|
| 794 |
+
|
| 795 |
+
// Exit edit mode
|
| 796 |
+
toggleEditMode(false);
|
| 797 |
+
|
| 798 |
+
// Optionally reload the patient list to update the timestamp/status in the list item
|
| 799 |
+
loadPatients(); // This will preserve selection via currentlySelectedPatientId logic
|
| 800 |
+
|
| 801 |
+
},
|
| 802 |
+
error: function(xhr, status, error) {
|
| 803 |
+
console.error("Save failed:", error, xhr.responseText);
|
| 804 |
+
let errorMessage = 'An error occurred while saving the care plan.';
|
| 805 |
+
if (xhr.responseJSON && xhr.responseJSON.error) {
|
| 806 |
+
errorMessage = xhr.responseJSON.error;
|
| 807 |
+
} else if (xhr.responseText) {
|
| 808 |
+
errorMessage += ": " + xhr.responseText;
|
| 809 |
+
} else {
|
| 810 |
+
errorMessage += ": " + error;
|
| 811 |
+
}
|
| 812 |
+
displayAlert('danger', `<i class="fas fa-times-circle me-2"></i> ${errorMessage}`, 8000); // Show error alert
|
| 813 |
+
|
| 814 |
+
// Re-enable save button, keep in edit mode
|
| 815 |
+
$('#save-plan-btn').prop('disabled', false).find('i').removeClass().addClass('fas fa-save me-2');
|
| 816 |
+
$('#edit-plan-btn').prop('disabled', false); // Re-enable edit button
|
| 817 |
+
$('#download-pdf').prop('disabled', false);
|
| 818 |
+
$('#resend-whatsapp-btn').prop('disabled', false);
|
| 819 |
+
$('#carePlanTabs button').prop('disabled', false); // Re-enable tabs
|
| 820 |
+
|
| 821 |
}
|
| 822 |
});
|
| 823 |
}
|
| 824 |
|
| 825 |
+
// Function to send WhatsApp message
|
| 826 |
+
function sendWhatsApp(patientId) {
|
| 827 |
+
console.log(`Attempting to send WhatsApp for patient ID: ${patientId}`);
|
| 828 |
+
// Disable button and show loader during send
|
| 829 |
+
$('#resend-whatsapp-btn').prop('disabled', true).find('i').removeClass().addClass('fas fa-spinner fa-spin me-2');
|
| 830 |
+
$('#edit-plan-btn').prop('disabled', true); // Also disable edit button
|
| 831 |
+
$('#save-plan-btn').prop('disabled', true); // Also disable save button if visible
|
| 832 |
+
$('#download-pdf').prop('disabled', true);
|
| 833 |
+
$('#carePlanTabs button').prop('disabled', true); // Disable tabs
|
| 834 |
+
|
| 835 |
+
|
| 836 |
+
$.ajax({
|
| 837 |
+
url: `/resend_whatsapp/${patientId}`,
|
| 838 |
+
type: 'POST',
|
| 839 |
+
success: function(response) {
|
| 840 |
+
console.log("WhatsApp send response:", response);
|
| 841 |
+
let alertType = 'success';
|
| 842 |
+
let alertMessage = '<i class="fab fa-whatsapp me-2"></i> WhatsApp message sent successfully!';
|
| 843 |
+
if (response.whatsapp_sent === false) {
|
| 844 |
+
alertType = 'warning';
|
| 845 |
+
alertMessage = '<i class="fas fa-exclamation-triangle me-2"></i> Failed to send WhatsApp message (check Twilio config or number).';
|
| 846 |
+
} else if (!response.success) { // Handle server-side error response (success: false)
|
| 847 |
+
alertType = 'danger';
|
| 848 |
+
alertMessage = '<i class="fas fa-times-circle me-2"></i> ' + (response.error || 'An error occurred sending WhatsApp.');
|
| 849 |
+
}
|
| 850 |
+
|
| 851 |
+
|
| 852 |
+
displayAlert(alertType, alertMessage, 8000); // Show alert
|
| 853 |
+
|
| 854 |
+
|
| 855 |
+
},
|
| 856 |
+
error: function(xhr, status, error) {
|
| 857 |
+
console.error("WhatsApp send failed:", error, xhr.responseText);
|
| 858 |
+
let errorMessage = 'An error occurred while sending the WhatsApp message.';
|
| 859 |
+
if (xhr.responseJSON && xhr.responseJSON.error) {
|
| 860 |
+
errorMessage = xhr.responseJSON.error;
|
| 861 |
+
} else if (xhr.responseText) {
|
| 862 |
+
errorMessage += ": " + xhr.responseText;
|
| 863 |
+
} else {
|
| 864 |
+
errorMessage += ": " + error;
|
| 865 |
+
}
|
| 866 |
+
displayAlert('danger', `<i class="fas fa-times-circle me-2"></i> ${errorMessage}`, 8000); // Show error alert
|
| 867 |
+
},
|
| 868 |
+
complete: function() {
|
| 869 |
+
// Always re-enable buttons after request completes
|
| 870 |
+
$('#resend-whatsapp-btn').prop('disabled', false).find('i').removeClass().addClass('fab fa-whatsapp me-2');
|
| 871 |
+
$('#edit-plan-btn').prop('disabled', false);
|
| 872 |
+
if (!isEditing) { // Only re-enable save if not in edit mode
|
| 873 |
+
$('#save-plan-btn').hide();
|
| 874 |
+
} else {
|
| 875 |
+
$('#save-plan-btn').prop('disabled', false);
|
| 876 |
+
}
|
| 877 |
+
$('#download-pdf').prop('disabled', false);
|
| 878 |
+
$('#carePlanTabs button').prop('disabled', false); // Re-enable tabs
|
| 879 |
+
}
|
| 880 |
+
});
|
| 881 |
+
}
|
| 882 |
+
|
| 883 |
+
// Function to display temporary alerts
|
| 884 |
+
function displayAlert(type, message, duration = 5000) {
|
| 885 |
+
const alertHtml = `
|
| 886 |
+
<div class="alert alert-${type} alert-dismissible fade show" role="alert">
|
| 887 |
+
${message}
|
| 888 |
+
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
| 889 |
+
</div>
|
| 890 |
+
`;
|
| 891 |
+
const $alert = $(alertHtml);
|
| 892 |
+
$('#plan-actions-alerts').append($alert);
|
| 893 |
+
|
| 894 |
+
// Auto-dismiss after duration
|
| 895 |
+
if (duration > 0) {
|
| 896 |
+
setTimeout(function() {
|
| 897 |
+
$alert.alert('close'); // Use Bootstrap's close method
|
| 898 |
+
}, duration);
|
| 899 |
+
}
|
| 900 |
+
}
|
| 901 |
+
|
| 902 |
+
|
| 903 |
+
// Handle patient deletion using event delegation
|
| 904 |
+
$('#patient-list').on('click', '.delete-patient-btn', function(event) {
|
| 905 |
+
event.preventDefault(); // Prevent default button click behavior
|
| 906 |
+
event.stopPropagation(); // Prevent the parent list item click handler from firing
|
| 907 |
+
|
| 908 |
+
const patientIdToDelete = $(this).data('id');
|
| 909 |
+
const $patientItem = $(this).closest('.patient-item');
|
| 910 |
+
const patientName = $patientItem.find('h6').text() || 'Unnamed Patient';
|
| 911 |
+
|
| 912 |
+
// Check if the patient being deleted is the one currently being edited
|
| 913 |
+
if (isEditing && currentlySelectedPatientId === patientIdToDelete) {
|
| 914 |
+
alert("Please save or cancel your edits before deleting this patient.");
|
| 915 |
+
return; // Prevent deletion while editing the same patient
|
| 916 |
+
}
|
| 917 |
+
// Check if the patient being deleted is the one currently selected (even if not editing)
|
| 918 |
+
if (currentlySelectedPatientId === patientIdToDelete) {
|
| 919 |
+
// If deleting the currently viewed patient, add an extra confirmation step
|
| 920 |
+
if (!confirm(`The record for ${patientName} is currently being viewed. Are you sure you want to delete it? This action cannot be undone.`)) {
|
| 921 |
+
console.log("Delete cancelled by user (currently viewing).");
|
| 922 |
+
return;
|
| 923 |
+
}
|
| 924 |
+
} else {
|
| 925 |
+
// For other patients, just a standard confirmation
|
| 926 |
+
if (!confirm(`Are you sure you want to delete the record for ${patientName}? This action cannot be undone.`)) {
|
| 927 |
+
console.log("Delete cancelled by user.");
|
| 928 |
+
return;
|
| 929 |
+
}
|
| 930 |
+
}
|
| 931 |
+
|
| 932 |
+
|
| 933 |
+
console.log(`Deleting patient ID: ${patientIdToDelete}`);
|
| 934 |
+
// Optionally show a spinner or disable the delete button
|
| 935 |
+
const $deleteBtnIcon = $(this).find('i');
|
| 936 |
+
$deleteBtnIcon.removeClass().addClass('fas fa-spinner fa-spin');
|
| 937 |
+
$(this).prop('disabled', true);
|
| 938 |
+
|
| 939 |
+
|
| 940 |
+
$.ajax({
|
| 941 |
+
url: `/delete_patient/${patientIdToDelete}`,
|
| 942 |
+
type: 'DELETE',
|
| 943 |
+
success: function(response) {
|
| 944 |
+
console.log("Delete successful:", response.message);
|
| 945 |
+
// Remove the patient item from the list
|
| 946 |
+
$patientItem.remove();
|
| 947 |
+
// Update patient count
|
| 948 |
+
$('#patient-count').text($('#patient-list li:visible').length); // Count visible items after potential search filter
|
| 949 |
+
// Check if the deleted patient was currently selected
|
| 950 |
+
if (currentlySelectedPatientId === patientIdToDelete) {
|
| 951 |
+
console.log("Deleted currently selected patient. Clearing details pane.");
|
| 952 |
+
resetDetailsPane(); // Use reset function
|
| 953 |
+
history.replaceState({}, '', '/doctor_dashboard'); // Clear URL param
|
| 954 |
+
}
|
| 955 |
+
// Check if the list is now empty
|
| 956 |
+
if ($('#patient-list li').length === 0) {
|
| 957 |
+
$('#no-patients-found').show();
|
| 958 |
+
}
|
| 959 |
+
|
| 960 |
+
// Reload the full patient list could also be an option here
|
| 961 |
+
// instead of just removing the element, to ensure sorting/alerts are updated
|
| 962 |
+
// loadPatients();
|
| 963 |
+
|
| 964 |
+
},
|
| 965 |
+
error: function(xhr, status, error) {
|
| 966 |
+
console.error("Delete failed:", error, xhr.responseText);
|
| 967 |
+
// Re-enable button and restore icon on error
|
| 968 |
+
$deleteBtnIcon.removeClass().addClass('fas fa-trash-alt');
|
| 969 |
+
$(this).prop('disabled', false);
|
| 970 |
+
|
| 971 |
+
let errorMessage = 'An error occurred during deletion.';
|
| 972 |
+
if (xhr.responseJSON && xhr.responseJSON.error) {
|
| 973 |
+
errorMessage = xhr.responseJSON.error;
|
| 974 |
+
} else if (xhr.responseText) {
|
| 975 |
+
errorMessage += ": " + xhr.responseText;
|
| 976 |
+
} else {
|
| 977 |
+
errorMessage += ": " + error;
|
| 978 |
+
}
|
| 979 |
+
alert(`Error deleting patient: ${errorMessage}`);
|
| 980 |
+
}
|
| 981 |
+
});
|
| 982 |
+
});
|
| 983 |
+
|
| 984 |
+
|
| 985 |
+
// Patient search functionality (updated count for filtered list)
|
| 986 |
$('#patient-search').on('input', function() {
|
| 987 |
const searchTerm = $(this).val().toLowerCase();
|
| 988 |
+
let foundCount = 0;
|
| 989 |
$('.patient-item').each(function() {
|
| 990 |
const patientName = $(this).find('h6').text().toLowerCase();
|
| 991 |
const patientDetails = $(this).find('small').text().toLowerCase(); // Includes age/gender/timestamp
|
| 992 |
if (patientName.includes(searchTerm) || patientDetails.includes(searchTerm)) {
|
| 993 |
$(this).show();
|
| 994 |
+
foundCount++;
|
| 995 |
} else {
|
| 996 |
$(this).hide();
|
| 997 |
}
|
| 998 |
});
|
| 999 |
+
$('#patient-count').text(foundCount); // Update count based on visible items
|
| 1000 |
+
if (foundCount > 0) {
|
| 1001 |
$('#no-patients-found').hide();
|
| 1002 |
} else {
|
| 1003 |
$('#no-patients-found').show();
|
|
|
|
| 1011 |
// Periodically check for emergency notifications (and refresh list if found)
|
| 1012 |
function checkEmergencyNotifications() {
|
| 1013 |
console.log("Checking for emergency notifications...");
|
| 1014 |
+
// Check if the current alert is an emergency alert before fetching again
|
| 1015 |
+
if ($('#emergency-alerts .alert-danger').length > 0) {
|
| 1016 |
+
// If an emergency alert is already shown, maybe check less frequently or skip some checks?
|
| 1017 |
+
// For now, always fetch to get potentially new emergencies or status changes.
|
| 1018 |
+
}
|
| 1019 |
+
|
| 1020 |
+
|
| 1021 |
$.ajax({
|
| 1022 |
url: '/get_emergency_notifications',
|
| 1023 |
type: 'GET',
|
| 1024 |
success: function(response) {
|
| 1025 |
if (response.success && response.notifications.length > 0) {
|
| 1026 |
console.log(`Found ${response.notifications.length} emergency notifications.`);
|
| 1027 |
+
const alertCount = response.notifications.length;
|
| 1028 |
+
// Simple check: does the number of emergencies match the current alert text?
|
| 1029 |
+
const currentAlertText = $('#emergency-alerts strong').text();
|
| 1030 |
+
const newAlertText = `${alertCount} Emergency patient${alertCount > 1 ? 's' : ''} requiring immediate attention!`;
|
| 1031 |
+
|
| 1032 |
+
if (currentAlertText !== newAlertText) {
|
| 1033 |
// Update emergency alerts section
|
|
|
|
| 1034 |
let alertHtml = `
|
| 1035 |
<div class="alert alert-danger mb-3">
|
| 1036 |
<i class="fas fa-exclamation-triangle me-2"></i>
|
| 1037 |
+
<strong>${newAlertText}</strong>
|
| 1038 |
</div>
|
| 1039 |
`;
|
| 1040 |
$('#emergency-alerts').html(alertHtml);
|
| 1041 |
// Reload the patient list to update sorting and potentially highlight new emergencies
|
|
|
|
| 1042 |
console.log("Reloading patient list due to new emergency alerts...");
|
| 1043 |
loadPatients(); // This will re-render the list and handle selection from URL/previously selected ID
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1044 |
}
|
| 1045 |
|
| 1046 |
} else {
|
|
|
|
| 1058 |
}
|
| 1059 |
|
| 1060 |
// Check for emergency notifications periodically (e.g., every 30 seconds)
|
|
|
|
| 1061 |
setInterval(checkEmergencyNotifications, 30000); // 30 seconds
|
| 1062 |
|
| 1063 |
// Handle browser back/forward button for URL patient ID
|
|
|
|
| 1065 |
const urlParams = new URLSearchParams(window.location.search);
|
| 1066 |
const patientIdFromUrl = urlParams.get('patient_id');
|
| 1067 |
|
| 1068 |
+
// Get the ID of the patient currently *displayed* in the details pane
|
| 1069 |
+
const currentlyDisplayedPatientId = $('#patient-details-section').is(':visible') ? currentlySelectedPatientId : null;
|
| 1070 |
+
|
| 1071 |
+
if (patientIdFromUrl && patientIdFromUrl !== currentlyDisplayedPatientId) {
|
| 1072 |
+
// If URL has an ID and it's different from the current one
|
| 1073 |
+
console.log(`URL changed to patient ID: ${patientIdFromUrl}. Attempting to load details.`);
|
| 1074 |
+
|
| 1075 |
+
// Prompt user if they have unsaved edits before navigating
|
| 1076 |
+
if (isEditing) {
|
| 1077 |
+
if (!confirm("You have unsaved changes. Discard changes and navigate to the selected patient?")) {
|
| 1078 |
+
// User cancelled, prevent navigation
|
| 1079 |
+
// Need to restore the original URL parameter
|
| 1080 |
+
history.pushState({ patient_id: currentlyDisplayedPatientId }, '', `/doctor_dashboard${currentlyDisplayedPatientId ? '?patient_id=' + currentlyDisplayedPatientId : ''}`);
|
| 1081 |
+
return; // Stop the popstate handling
|
| 1082 |
+
}
|
| 1083 |
+
isEditing = false; // User confirmed discard
|
| 1084 |
+
}
|
| 1085 |
|
| 1086 |
+
|
| 1087 |
+
// Check if this ID exists in the currently rendered list before calling API
|
|
|
|
|
|
|
| 1088 |
if ($(`.patient-item[data-id="${patientIdFromUrl}"]`).length > 0) {
|
| 1089 |
loadPatientDetails(patientIdFromUrl);
|
| 1090 |
} else {
|
| 1091 |
console.warn(`Patient ID "${patientIdFromUrl}" from URL not found in current list.`);
|
| 1092 |
+
resetDetailsPane(); // Use reset function
|
| 1093 |
+
history.replaceState({}, '', '/doctor_dashboard'); // Clear URL param if not found
|
|
|
|
| 1094 |
}
|
| 1095 |
+
} else if (!patientIdFromUrl && currentlyDisplayedPatientId) {
|
| 1096 |
// If URL has no ID but a patient is currently displayed, hide details
|
| 1097 |
console.log("URL changed, no patient ID. Hiding details.");
|
| 1098 |
+
// Prompt user if they have unsaved edits before navigating away
|
| 1099 |
+
if (isEditing) {
|
| 1100 |
+
if (!confirm("You have unsaved changes. Discard changes and navigate away?")) {
|
| 1101 |
+
// User cancelled, prevent navigation
|
| 1102 |
+
// Need to restore the original URL parameter
|
| 1103 |
+
history.pushState({ patient_id: currentlyDisplayedPatientId }, '', `/doctor_dashboard${currentlyDisplayedPatientId ? '?patient_id=' + currentlyDisplayedPatientId : ''}`);
|
| 1104 |
+
return; // Stop the popstate handling
|
| 1105 |
+
}
|
| 1106 |
+
isEditing = false; // User confirmed discard
|
| 1107 |
+
}
|
| 1108 |
+
resetDetailsPane(); // Use reset function
|
| 1109 |
+
} else if (patientIdFromUrl && patientIdFromUrl === currentlyDisplayedPatientId) {
|
| 1110 |
+
// If URL has the same ID as currently displayed, and not in edit mode, do nothing
|
| 1111 |
+
if (!isEditing) {
|
| 1112 |
+
console.log("URL changed but same patient ID. No action needed.");
|
| 1113 |
+
} else {
|
| 1114 |
+
// If in edit mode, and URL is same, user might have just saved/cancelled edits
|
| 1115 |
+
// The loadPatientDetails function (called on save success) handles resetting state.
|
| 1116 |
+
// If user somehow manually triggered popstate while editing without saving/cancelling...
|
| 1117 |
+
// We could re-confirm, but the loadPatients/loadPatientDetails calls triggered on page load
|
| 1118 |
+
// already handle initial state. This case is less common via standard browser UI.
|
| 1119 |
+
console.log("URL same, in edit mode. State should be handled by save/cancel flow.");
|
| 1120 |
+
// Optionally, if state seems inconsistent, force a reload:
|
| 1121 |
+
// loadPatientDetails(patientIdFromUrl);
|
| 1122 |
+
}
|
| 1123 |
} else {
|
| 1124 |
+
// If neither URL nor displayed patient has an ID (e.g., went back to dashboard root from a state with no patient selected)
|
| 1125 |
console.log("URL changed, no patient selected.");
|
| 1126 |
+
// No need to prompt for edits if no patient was selected/displayed
|
| 1127 |
+
resetDetailsPane(); // Use reset function
|
|
|
|
|
|
|
| 1128 |
}
|
| 1129 |
};
|
| 1130 |
});
|