Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Patient Care Management System</title> | |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css"> | |
| <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --primary-color: #4e73df; /* Blue */ | |
| --secondary-color: #1cc88a; /* Green */ | |
| --danger-color: #e74a3b; /* Red */ | |
| --warning-color: #f6c23e; /* Yellow */ | |
| --info-color: #36b9cc; /* Cyan */ | |
| --dark-color: #5a5c69; /* Dark Grey */ | |
| --light-bg: #f8f9fc; /* Light Grey Background */ | |
| --border-color: #e3e6f0; /* Light Border */ | |
| } | |
| body { | |
| font-family: 'Nunito', sans-serif; | |
| background-color: var(--light-bg); | |
| color: var(--dark-color); | |
| } | |
| .navbar { | |
| background-color: white; | |
| box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15); | |
| } | |
| .navbar-brand { | |
| font-weight: 800; | |
| color: var(--primary-color) ; /* Override default link color */ | |
| } | |
| .card { | |
| border: none; | |
| box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15); | |
| border-radius: 0.35rem; | |
| /* transition: transform 0.3s ease; Removed for form card */ | |
| background-color: white; | |
| } | |
| .card:hover { | |
| /* transform: translateY(-5px); Remove slight hover lift for form card */ | |
| } | |
| .card-header { | |
| background-color: var(--light-bg); | |
| border-bottom: 1px solid var(--border-color); | |
| font-weight: 700; | |
| color: var(--dark-color); | |
| padding: 1rem 1.25rem; | |
| } | |
| .btn-primary { | |
| background-color: var(--primary-color); | |
| border-color: var(--primary-color); | |
| transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out; | |
| } | |
| .btn-primary:hover { | |
| background-color: #2e59d9; /* Darker blue */ | |
| border-color: #2653d4; | |
| } | |
| .btn-success { | |
| background-color: var(--secondary-color); | |
| border-color: var(--secondary-color); | |
| } | |
| .btn-danger { | |
| background-color: var(--danger-color); | |
| border-color: var(--danger-color); | |
| } | |
| .form-control:focus { | |
| border-color: var(--primary-color); | |
| box-shadow: 0 0 0 0.25rem rgba(78, 115, 223, 0.25); | |
| } | |
| .form-select:focus { | |
| border-color: var(--primary-color); | |
| box-shadow: 0 0 0 0.25rem rgba(78, 115, 223, 0.25); | |
| } | |
| .role-toggle { | |
| display: flex; | |
| align-items: center; | |
| } | |
| .role-toggle-btn { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| background-color: var(--light-bg); | |
| border: 1px solid var(--border-color); | |
| border-radius: 30px; | |
| padding: 0.5rem 1rem; | |
| font-weight: 600; | |
| color: var(--dark-color); | |
| transition: all 0.3s ease; | |
| text-decoration: none; /* Remove underline */ | |
| } | |
| .role-toggle-btn:hover { | |
| background-color: #edf2ff; /* Lighter blue */ | |
| } | |
| .role-toggle-btn.active { | |
| background-color: var(--primary-color); | |
| color: white; | |
| border-color: var(--primary-color); | |
| } | |
| .main-content { | |
| padding: 1.5rem; /* Reduced padding */ | |
| } | |
| .status-badge { | |
| padding: 0.5rem 1rem; | |
| border-radius: 50rem; | |
| color: white; | |
| font-weight: 700; | |
| display: inline-block; /* Added to make it work better with margins */ | |
| margin-right: 1rem; | |
| } | |
| .status-emergency { | |
| background-color: var(--danger-color); | |
| } | |
| .status-deteriorating { | |
| background-color: var(--warning-color); | |
| color: #212529; /* Dark text for yellow */ | |
| } | |
| .status-improving { | |
| background-color: var(--secondary-color); | |
| } | |
| .status-stable { | |
| background-color: var(--info-color); | |
| } | |
| .status-unknown { | |
| background-color: var(--dark-color); | |
| color: white; | |
| } | |
| .patient-form-container { | |
| max-width: 800px; | |
| margin: 0 auto; | |
| } | |
| .upload-box { | |
| border: 2px dashed var(--border-color); | |
| border-radius: 5px; | |
| padding: 1.5rem; /* Slightly less padding */ | |
| text-align: center; | |
| margin-bottom: 1rem; | |
| background-color: #fcfcfc; | |
| transition: border-color 0.2s ease-in-out; | |
| cursor: pointer; /* Indicate it's clickable */ | |
| } | |
| .upload-box:hover { | |
| border-color: var(--primary-color); | |
| } | |
| .upload-box input[type="file"] { | |
| display: none; /* Hide the actual file input */ | |
| } | |
| .upload-box label { | |
| cursor: pointer; /* Ensure label is clickable */ | |
| } | |
| #feedback { | |
| min-height: 150px; | |
| } | |
| .care-plan-output { | |
| background-color: var(--light-bg); | |
| border-radius: 0.35rem; | |
| border: 1px solid var(--border-color); | |
| padding: 1.5rem; | |
| margin-top: 1.5rem; /* Less margin */ | |
| white-space: pre-line; | |
| word-wrap: break-word; /* Prevent overflow */ | |
| overflow-x: auto; /* Add horizontal scroll if needed */ | |
| font-size: 1rem; | |
| color: #333; | |
| } | |
| .loader { | |
| border: 5px solid #f3f3f3; | |
| border-top: 5px solid var(--primary-color); | |
| border-radius: 50%; | |
| width: 50px; | |
| height: 50px; | |
| animation: spin 2s linear infinite; | |
| margin: 2rem auto; | |
| display: none; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| #pdf-viewer { | |
| margin-top: 1.5rem; /* Less margin */ | |
| border: 1px solid var(--border-color); | |
| border-radius: 0.35rem; | |
| background-color: white; | |
| overflow: hidden; /* Hide scrollbars from parent */ | |
| } | |
| #pdf-viewer object { | |
| display: block; /* Remove extra space below object */ | |
| } | |
| .success-alert { | |
| display: none; | |
| margin-top: 1.5rem; /* Less margin */ | |
| } | |
| .section-title { | |
| color: var(--dark-color); /* Used dark color for title */ | |
| font-weight: 700; | |
| margin-bottom: 1.5rem; | |
| position: relative; | |
| padding-bottom: 0.5rem; | |
| font-size: 1.5rem; /* Larger font size */ | |
| } | |
| .section-title::after { | |
| content: ''; | |
| position: absolute; | |
| left: 0; | |
| bottom: 0; | |
| height: 3px; | |
| width: 60px; /* Wider underline */ | |
| background-color: var(--primary-color); | |
| border-radius: 1.5px; | |
| } | |
| .results-container { | |
| margin-top: 2rem; | |
| border-top: 1px dashed var(--border-color); /* Separator line */ | |
| padding-top: 2rem; | |
| display: none; /* Hidden by default */ | |
| } | |
| .alert-success { | |
| color: #0f5132; | |
| background-color: #d1e7dd; | |
| border-color: #badbcc; | |
| } | |
| .alert-success strong { | |
| color: var(--secondary-color); | |
| } | |
| .alert-danger { | |
| color: #721c24; | |
| background-color: #f8d7da; | |
| border-color: #f5c6cb; | |
| } | |
| .alert-danger strong { | |
| color: var(--danger-color); | |
| } | |
| /* Style for PDF preview container if PDF not shown */ | |
| #pdf-viewer.placeholder { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 200px; /* Give it some height */ | |
| color: #6c757d; /* Muted text */ | |
| font-style: italic; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Navigation Bar --> | |
| <nav class="navbar navbar-expand-lg navbar-light bg-white px-4 mb-4"> | |
| <div class="container-fluid"> | |
| <a class="navbar-brand" href="/"> | |
| <i class="fas fa-heartbeat me-2"></i> | |
| Patient Care Management | |
| </a> | |
| <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> | |
| <span class="navbar-toggler-icon"></span> | |
| </button> | |
| <div class="collapse navbar-collapse" id="navbarNav"> | |
| <ul class="navbar-nav ms-auto"> | |
| <li class="nav-item"> | |
| <a class="nav-link" href="/" role="button">Home</a> | |
| </li> | |
| <li class="nav-item"> | |
| <div class="role-toggle"> | |
| <a href="/" class="role-toggle-btn active" data-role="patient"> | |
| <i class="fas fa-user"></i> Patient | |
| </a> | |
| <a href="/doctor_dashboard" class="role-toggle-btn ms-2" data-role="doctor"> | |
| <i class="fas fa-user-md"></i> Doctor | |
| </a> | |
| </div> | |
| </li> | |
| </ul> | |
| </div> | |
| </div> | |
| </nav> | |
| <div class="container main-content"> | |
| <!-- Patient Form --> | |
| <div id="feedback-form" class="patient-form-container"> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h5 class="mb-0"><i class="fas fa-notes-medical me-2"></i>Submit Patient Feedback</h5> | |
| </div> | |
| <div class="card-body"> | |
| <form id="patientForm"> | |
| <div class="row mb-3"> | |
| <div class="col-md-4"> | |
| <label for="name" class="form-label">Patient Name</label> | |
| <input type="text" class="form-control" id="name" name="name" required> | |
| </div> | |
| <div class="col-md-4"> | |
| <label for="age" class="form-label">Age</label> | |
| <input type="number" class="form-control" id="age" name="age"> | |
| </div> | |
| <div class="col-md-4"> | |
| <label for="gender" class="form-label">Gender</label> | |
| <select class="form-select" id="gender" name="gender"> | |
| <option value="">Select Gender (Optional)</option> | |
| <option value="Male">Male</option> | |
| <option value="Female">Female</option> | |
| <option value="Other">Other</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="mb-3"> | |
| <label class="form-label"><i class="fas fa-file-pdf me-1"></i>Current Care Plan (Optional PDF)</label> | |
| <div class="upload-box"> | |
| <i class="fas fa-upload fa-2x mb-3" style="color: var(--info-color);"></i> | |
| <h5>Drag & Drop or Click to Upload PDF</h5> | |
| <p class="text-muted mb-0">Accepted format: PDF</p> | |
| <input type="file" id="care_plan_pdf" name="care_plan_pdf" accept=".pdf"> | |
| <label for="care_plan_pdf" class="btn btn-outline-primary mt-2"><i class="fas fa-folder-open me-2"></i>Browse Files</label> | |
| <div id="file-name" class="mt-2 text-muted small">No file selected</div> | |
| </div> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="feedback" class="form-label"><i class="fas fa-comment-medical me-1"></i>Current Symptoms & Feedback <span class="text-danger">*</span></label> | |
| <textarea class="form-control" id="feedback" name="feedback" rows="5" placeholder="Please describe your current symptoms, any changes in your condition, and feedback about your current care plan..." required></textarea> | |
| </div> | |
| <button type="submit" class="btn btn-primary w-100 py-2" id="submit-btn"> | |
| <i class="fas fa-sync-alt me-2"></i>Generate Updated Care Plan | |
| </button> | |
| </form> | |
| <div class="loader" id="loader"></div> | |
| <div id="api-error-alert" class="alert alert-danger mt-3" style="display: none;"> | |
| <i class="fas fa-exclamation-circle me-2"></i> <strong>Error:</strong> <span id="api-error-message"></span> | |
| </div> | |
| <div class="alert alert-success success-alert" id="whatsapp-alert"> | |
| <i class="fab fa-whatsapp me-2"></i> <strong>Success!</strong> Your updated care plan has been sent to your WhatsApp number (if configured). | |
| </div> | |
| <div class="alert alert-warning success-alert" id="whatsapp-fail-alert" style="display: none;"> | |
| <i class="fab fa-whatsapp me-2"></i> <strong>Note:</strong> Could not send WhatsApp message (Twilio not configured or error). | |
| </div> | |
| <!-- Results Section --> | |
| <div id="results" class="results-container" style="display: none;"> | |
| <h4 class="section-title">Generated Care Plan Result</h4> | |
| <div class="d-flex justify-content-between align-items-center mb-3"> | |
| <div>Status: <span id="status-badge" class="status-badge"></span></div> | |
| <button id="download-pdf" class="btn btn-primary" disabled> | |
| <i class="fas fa-file-download me-2"></i>Download PDF | |
| </button> | |
| </div> | |
| <h5>Generated Plan Text:</h5> | |
| <div class="care-plan-output" id="care-plan-output"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> | |
| <script> | |
| $(document).ready(function() { | |
| // Set active role button on load | |
| $('.role-toggle-btn[data-role="patient"]').addClass('active'); | |
| $('.role-toggle-btn[data-role="doctor"]').removeClass('active'); | |
| // Handle file upload display | |
| $('#care_plan_pdf').change(function() { | |
| if (this.files && this.files.length > 0) { | |
| $('#file-name').text(this.files[0].name); | |
| } else { | |
| $('#file-name').text('No file selected'); | |
| } | |
| }); | |
| // Handle drag and drop for upload box | |
| const uploadBox = $('.upload-box'); | |
| uploadBox.on('dragover', function(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| $(this).addClass('border-primary'); | |
| }); | |
| uploadBox.on('dragleave', function(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| $(this).removeClass('border-primary'); | |
| }); | |
| uploadBox.on('drop', function(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| $(this).removeClass('border-primary'); | |
| const files = e.originalEvent.dataTransfer.files; | |
| if (files.length > 0 && files[0].type === 'application/pdf') { | |
| $('#care_plan_pdf')[0].files = files; | |
| $('#file-name').text(files[0].name); | |
| } else { | |
| alert("Only PDF files are allowed."); | |
| $('#file-name').text('No file selected'); | |
| } | |
| }); | |
| // Prevent default drag/drop on body to avoid opening file | |
| $('body').on('dragover', function(e) { e.preventDefault(); e.stopPropagation(); }); | |
| $('body').on('drop', function(e) { e.preventDefault(); e.stopPropagation(); }); | |
| // Form submission | |
| $('#patientForm').submit(function(e) { | |
| e.preventDefault(); | |
| // Clear previous results, alerts, hide API error | |
| $('#loader').show(); | |
| $('#submit-btn').prop('disabled', true).find('i').removeClass().addClass('fas fa-spinner fa-spin me-2'); // Disable button & show spinner | |
| $('#results').hide(); | |
| $('#whatsapp-alert').hide(); | |
| $('#whatsapp-fail-alert').hide(); | |
| $('#api-error-alert').hide(); | |
| $('#pdf-viewer').html('<p class="text-muted text-center p-4">Generating PDF preview...</p>').removeClass('placeholder'); // Clear previous PDF preview and add placeholder | |
| $('#download-pdf').prop('disabled', true); // Disable download button | |
| // Create FormData object for file upload | |
| const formData = new FormData(this); | |
| // Send AJAX request | |
| $.ajax({ | |
| url: '/submit_feedback', | |
| type: 'POST', | |
| data: formData, | |
| contentType: false, // Required for FormData | |
| processData: false, // Required for FormData | |
| success: function(response) { | |
| $('#loader').hide(); | |
| $('#submit-btn').prop('disabled', false).find('i').removeClass().addClass('fas fa-sync-alt me-2'); // Re-enable button & restore icon | |
| if (response.success) { | |
| console.log("Submission successful:", response); | |
| // Store patient ID for download button | |
| const patientId = response.patient_id; | |
| // Display results section | |
| $('#results').fadeIn(); | |
| $('#care-plan-output').text(response.updated_plan); | |
| // Set status badge | |
| const statusText = { | |
| 'emergency': 'EMERGENCY', | |
| 'deteriorating': 'HIGH RISK', | |
| 'improving': 'IMPROVING', | |
| 'stable': 'STABLE', | |
| 'unknown': 'UNKNOWN' | |
| }; | |
| const statusBadgeClass = { | |
| 'emergency': 'status-emergency', | |
| 'deteriorating': 'status-deteriorating', | |
| 'improving': 'status-improving', | |
| 'stable': 'status-stable', | |
| 'unknown': 'status-unknown' | |
| }; | |
| $('#status-badge') | |
| .text(statusText[response.status] || 'UNKNOWN') | |
| .attr('class', 'status-badge ' + (statusBadgeClass[response.status] || 'status-unknown')); | |
| // Set up PDF download button with patient ID | |
| $('#download-pdf').prop('disabled', false); // Enable download button | |
| $('#download-pdf').off('click').on('click', function() { // Use .off().on() to prevent multiple handlers | |
| console.log("Download PDF button clicked, ID:", patientId); | |
| // Direct link for download | |
| window.location.href = '/download_pdf/' + patientId; | |
| }); | |
| // Display PDF preview if available (using base64 data from response) | |
| if (response.pdf_data) { | |
| const pdfData = `data:application/pdf;base64,${response.pdf_data}`; | |
| // Use <object> for embedding PDF | |
| $('#pdf-viewer').html(`<object data="${pdfData}" type="application/pdf" width="100%" height="500px"> | |
| <p>Your browser does not support PDFs. You can download the PDF instead.</p> | |
| </object>`).removeClass('placeholder'); | |
| console.log("PDF preview embedded."); | |
| } else { | |
| $('#pdf-viewer').html('<p class="text-muted text-center p-4">PDF preview not available.</p>').addClass('placeholder'); | |
| console.log("PDF data missing or error during PDF generation on server."); | |
| } | |
| // Show WhatsApp notification | |
| if (response.whatsapp_sent === true) { | |
| $('#whatsapp-alert').fadeIn(); | |
| } else if (response.whatsapp_sent === false) { | |
| $('#whatsapp-fail-alert').fadeIn(); | |
| } | |
| // Clear the form after successful submission | |
| $('#patientForm')[0].reset(); | |
| $('#file-name').text('No file selected'); // Clear file name display | |
| } else { | |
| console.error("Submission failed:", response.error); | |
| $('#api-error-message').text(response.error || 'An unknown error occurred.'); | |
| $('#api-error-alert').fadeIn(); | |
| // If AI generation failed but fallback plan provided | |
| if(response.fallback_plan){ | |
| $('#results').fadeIn(); | |
| $('#care-plan-output').text(response.fallback_plan); | |
| // Set status badge (using status from fallback response) | |
| const statusText = { | |
| 'emergency': 'EMERGENCY', | |
| 'deteriorating': 'HIGH RISK', | |
| 'improving': 'IMPROVING', | |
| 'stable': 'STABLE', | |
| 'unknown': 'UNKNOWN' | |
| }; | |
| const statusBadgeClass = { | |
| 'emergency': 'status-emergency', | |
| 'deteriorating': 'status-deteriorating', | |
| 'improving': 'status-improving', | |
| 'stable': 'status-stable', | |
| 'unknown': 'status-unknown' | |
| }; | |
| $('#status-badge') | |
| .text(statusText[response.status] || 'UNKNOWN') | |
| .attr('class', 'status-badge ' + (statusBadgeClass[response.status] || 'status-unknown')); | |
| // No PDF data in error response, show placeholder | |
| $('#pdf-viewer').html('<p class="text-muted text-center p-4">PDF preview not available due to generation error.</p>').addClass('placeholder'); | |
| $('#download-pdf').prop('disabled', true); // Keep download disabled | |
| } | |
| } | |
| }, | |
| error: function(xhr, status, error) { | |
| $('#loader').hide(); | |
| $('#submit-btn').prop('disabled', false).find('i').removeClass().addClass('fas fa-sync-alt me-2'); // Re-enable button & restore icon | |
| $('#download-pdf').prop('disabled', true); // Disable download button | |
| let errorMessage = 'An error occurred. Please try again.'; | |
| if (xhr.responseJSON && xhr.responseJSON.error) { | |
| errorMessage = xhr.responseJSON.error; | |
| } else if (xhr.responseText) { | |
| errorMessage += ": " + xhr.responseText; // Show raw error if not specific JSON | |
| } else { | |
| errorMessage += ": " + error; | |
| } | |
| console.error("AJAX error:", status, error, xhr.responseText); | |
| $('#api-error-message').text(errorMessage); | |
| $('#api-error-alert').fadeIn(); | |
| $('#pdf-viewer').html('<p class="text-muted text-center p-4">PDF preview not available due to error.</p>').addClass('placeholder'); | |
| } | |
| }); | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |