Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Faculty Data Analysis</title> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> | |
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| /* Modern and Enhanced Styles */ | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background: linear-gradient(135deg, #18579693 0%, #0c5db9 100%); /* Light professional gradient */ | |
| color: #415880; | |
| line-height: 1.6; | |
| } | |
| /* Add this for a subtle pattern overlay */ | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-image: | |
| linear-gradient(45deg, rgba(255, 255, 255, 0.1) 25%, transparent 25%), | |
| linear-gradient(-45deg, rgba(255, 255, 255, 0.1) 25%, transparent 25%), | |
| linear-gradient(45deg, transparent 75%, rgba(255, 255, 255, 0.1) 75%), | |
| linear-gradient(-45deg, transparent 75%, rgba(255, 255, 255, 0.1) 75%); | |
| background-size: 20px 20px; | |
| z-index: -1; | |
| } | |
| /* Update card and form backgrounds for better contrast */ | |
| .card, form { | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(10px); | |
| } | |
| /* Update header colors for better visibility */ | |
| h1 { | |
| background: linear-gradient(120deg, #2c5282, #2b6cb0); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| h2 { | |
| color: #2d3748; | |
| } | |
| /* Update accordion headers */ | |
| .accordion-button:not(.collapsed) { | |
| background: linear-gradient(135deg, #2c5282 0%, #2b6cb0 100%); | |
| color: white; | |
| } | |
| /* Update card headers */ | |
| .card-header { | |
| background: linear-gradient(135deg, #2c5282 0%, #2b6cb0 100%); | |
| color: white; | |
| } | |
| .container { | |
| max-width: 1400px; | |
| margin: 40px auto; | |
| padding: 0 30px; | |
| } | |
| h1 { | |
| font-size: 3rem; | |
| font-weight: 700; | |
| background: #1e3c6d; | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| animation: fadeIn 1s ease-in; | |
| } | |
| h2 { | |
| font-size: 2.2rem; | |
| color: #1a365d; | |
| font-weight: 600; | |
| margin: 2rem 0; | |
| text-align: center; | |
| } | |
| /* Enhanced Form Styles */ | |
| form { | |
| background: rgba(255, 255, 255, 0.9); | |
| backdrop-filter: blur(10px); | |
| padding: 2.5rem; | |
| border-radius: 20px; | |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); | |
| margin-bottom: 3rem; | |
| transition: all 0.3s ease; | |
| } | |
| form:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15); | |
| border-radius: 25px solid #1e3c6d; | |
| } | |
| .form-label { | |
| font-weight: 600; | |
| color: #4a5568; | |
| margin-bottom: 0.75rem; | |
| font-size: 1.1rem; | |
| } | |
| .form-control { | |
| border: 2px solid #e2e8f0; | |
| border-radius: 12px; | |
| padding: 1rem; | |
| transition: all 0.3s ease; | |
| } | |
| .form-control:focus { | |
| border-color: #3182ce; | |
| box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.2); | |
| } | |
| /* Modern Button Styles */ | |
| .btn { | |
| padding: 1rem 2rem; | |
| border-radius: 12px; | |
| font-weight: 600; | |
| letter-spacing: 0.5px; | |
| transition: all 0.3s ease; | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%); | |
| border: none; | |
| box-shadow: 0 4px 15px rgba(37, 99, 235, 0.2); | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 20px rgba(37, 99, 235, 0.3); | |
| background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%); | |
| } | |
| /* Enhanced Table Styles */ | |
| .table-container { | |
| background: white; | |
| border-radius: 20px; | |
| box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1); | |
| overflow: hidden; | |
| margin-bottom: 2rem; | |
| } | |
| .table { | |
| margin-bottom: 0; | |
| } | |
| .table thead th { | |
| background: linear-gradient(135deg, #063292 0%, #3b82f6 100%); | |
| color: white; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| font-size: 0.9rem; | |
| letter-spacing: 1px; | |
| padding: 1.2rem solid #063292; | |
| border: none; | |
| } | |
| .table tbody tr { | |
| transition: all 0.2s ease; | |
| } | |
| .table tbody tr:hover { | |
| background-color: #f8fafc; | |
| transform: scale(1.01); | |
| } | |
| /* Card Styles for Insights */ | |
| .card { | |
| border: none; | |
| border-radius: 20px; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); | |
| transition: all 0.3s ease; | |
| overflow: hidden; | |
| } | |
| .card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 15px 35px rgba(0, 0, 0, 0.12); | |
| } | |
| .card-header { | |
| background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%); | |
| color: white; | |
| font-weight: 600; | |
| padding: 1.2rem; | |
| border: none; | |
| } | |
| .card-body { | |
| padding: 1.5rem; | |
| } | |
| /* Accordion Styles */ | |
| .accordion-item { | |
| border: none; | |
| margin-bottom: 1rem; | |
| border-radius: 15px; | |
| overflow: hidden; | |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); | |
| } | |
| .accordion-button { | |
| padding: 1.2rem; | |
| font-weight: 600; | |
| background: white; | |
| } | |
| .accordion-button:not(.collapsed) { | |
| background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%); | |
| color: white; | |
| } | |
| /* Animation Classes */ | |
| .fade-in { | |
| animation: fadeIn 1s ease-in; | |
| } | |
| .slide-up { | |
| animation: slideInUp 0.5s ease-out; | |
| } | |
| /* Responsive Design */ | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 0 15px; | |
| } | |
| h1 { | |
| font-size: 2.2rem; | |
| } | |
| h2 { | |
| font-size: 1.8rem; | |
| } | |
| form { | |
| padding: 1.5rem; | |
| } | |
| .btn { | |
| padding: 0.8rem 1.5rem; | |
| } | |
| } | |
| /* Custom Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 10px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #3b82f6; | |
| border-radius: 5px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #2563eb; | |
| } | |
| /* Modern Variables */ | |
| :root { | |
| --primary-color: #2563eb; | |
| --secondary-color: #3b82f6; | |
| --accent-color: #7c3aed; | |
| --success-color: #10b981; | |
| --warning-color: #f59e0b; | |
| --danger-color: #ef4444; | |
| --background-color: #f8fafc; | |
| --card-bg: #ffffff; | |
| --text-primary: #1e293b; | |
| --text-secondary: #64748b; | |
| --border-radius: 12px; | |
| --transition: all 0.3s ease; | |
| } | |
| /* Enhanced Base Styles */ | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background: linear-gradient(135deg, var(--background-color) 0%, #e2e8f0 100%); | |
| color: var(--text-primary); | |
| line-height: 1.6; | |
| } | |
| .container { | |
| max-width: 1400px; | |
| margin: 40px auto; | |
| padding: 0 30px; | |
| } | |
| /* Enhanced Form Elements */ | |
| .form-control, .form-select { | |
| border: 2px solid #e2e8f0; | |
| border-radius: var(--border-radius); | |
| padding: 0.75rem 1rem; | |
| transition: var(--transition); | |
| background-color: rgba(255, 255, 255, 0.9); | |
| } | |
| .form-control:focus, .form-select:focus { | |
| border-color: var(--primary-color); | |
| box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); | |
| transform: translateY(-1px); | |
| } | |
| /* Custom Range Slider */ | |
| .custom-range { | |
| -webkit-appearance: none; | |
| width: 100%; | |
| height: 8px; | |
| border-radius: 5px; | |
| background: #e2e8f0; | |
| outline: none; | |
| margin: 15px 0; | |
| } | |
| .custom-range::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 24px; | |
| height: 24px; | |
| border-radius: 50%; | |
| background: var(--primary-color); | |
| cursor: pointer; | |
| transition: var(--transition); | |
| border: 2px solid white; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| } | |
| .custom-range::-webkit-slider-thumb:hover { | |
| transform: scale(1.1); | |
| } | |
| /* Range Value Display */ | |
| .range-value { | |
| text-align: center; | |
| font-weight: 600; | |
| color: var(--primary-color); | |
| margin-top: 8px; | |
| } | |
| /* Checkbox Group */ | |
| .checkbox-group { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 1rem; | |
| margin-top: 0.5rem; | |
| } | |
| .form-check { | |
| padding: 0.5rem; | |
| border-radius: var(--border-radius); | |
| transition: var(--transition); | |
| } | |
| .form-check:hover { | |
| background: rgba(37, 99, 235, 0.05); | |
| } | |
| .form-check-input { | |
| width: 1.2em; | |
| height: 1.2em; | |
| margin-top: 0.2em; | |
| cursor: pointer; | |
| } | |
| .form-check-input:checked { | |
| background-color: var(--primary-color); | |
| border-color: var(--primary-color); | |
| } | |
| /* Card Enhancements */ | |
| .card { | |
| border: none; | |
| border-radius: var(--border-radius); | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); | |
| transition: var(--transition); | |
| overflow: hidden; | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(10px); | |
| } | |
| .card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 15px 35px rgba(0, 0, 0, 0.12); | |
| } | |
| /* Section Headers */ | |
| h4 { | |
| color: var(--text-primary); | |
| font-weight: 600; | |
| margin-bottom: 1.5rem; | |
| padding-bottom: 0.5rem; | |
| border-bottom: 2px solid var(--primary-color); | |
| display: inline-block; | |
| } | |
| /* Textarea Enhancement */ | |
| textarea.form-control { | |
| resize: vertical; | |
| min-height: 100px; | |
| } | |
| /* Animation Classes */ | |
| .fade-in { | |
| animation: fadeIn 0.5s ease-in; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* Responsive Design */ | |
| @media (max-width: 768px) { | |
| .checkbox-group { | |
| grid-template-columns: 1fr; | |
| } | |
| .container { | |
| padding: 0 15px; | |
| } | |
| .card { | |
| margin: 1rem 0; | |
| } | |
| } | |
| /* Loading State */ | |
| .loading { | |
| position: relative; | |
| opacity: 0.8; | |
| pointer-events: none; | |
| } | |
| .loading::after { | |
| content: ""; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| width: 2rem; | |
| height: 2rem; | |
| border: 3px solid #f3f3f3; | |
| border-top: 3px solid var(--primary-color); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| transform: translate(-50%, -50%); | |
| } | |
| @keyframes spin { | |
| 0% { transform: translate(-50%, -50%) rotate(0deg); } | |
| 100% { transform: translate(-50%, -50%) rotate(360deg); } | |
| } | |
| /* Print Button Styling */ | |
| .print-btn { | |
| position: fixed; | |
| bottom: 30px; | |
| right: 30px; | |
| z-index: 1000; | |
| background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); | |
| color: white; | |
| border: none; | |
| padding: 12px 24px; | |
| border-radius: 50px; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); | |
| transition: all 0.3s ease; | |
| } | |
| .print-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); | |
| background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); | |
| color: white; | |
| } | |
| /* Print Media Styles */ | |
| @media print { | |
| .print-btn { | |
| display: none; | |
| } | |
| /* Hide elements not needed in print */ | |
| .btn-primary, | |
| .form-control, | |
| input[type="file"], | |
| .accordion-button { | |
| display: none ; | |
| } | |
| /* Ensure all content is visible */ | |
| .accordion-collapse { | |
| display: block ; | |
| } | |
| /* Better page breaks */ | |
| .card, | |
| .table-responsive, | |
| .accordion-item { | |
| page-break-inside: avoid; | |
| } | |
| /* Adjust colors for better printing */ | |
| body { | |
| background: white ; | |
| color: black ; | |
| } | |
| /* Ensure text contrast */ | |
| h1, h2, h3, h4 { | |
| color: black ; | |
| -webkit-text-fill-color: black ; | |
| } | |
| /* Adjust table styling for print */ | |
| .table { | |
| border: 1px solid #ddd ; | |
| } | |
| .table th, | |
| .table td { | |
| border: 1px solid #ddd ; | |
| } | |
| /* Ensure graphs are properly sized */ | |
| .plotly-graph-div { | |
| width: 100% ; | |
| height: auto ; | |
| } | |
| /* Add page numbers */ | |
| @page { | |
| margin: 2cm; | |
| } | |
| body::after { | |
| content: counter(page); | |
| counter-increment: page; | |
| position: fixed; | |
| bottom: 0; | |
| right: 0; | |
| font-size: 12px; | |
| } | |
| } | |
| /* Add this to your existing styles */ | |
| .fa-sync-alt { | |
| transition: transform 0.3s ease; | |
| } | |
| .fa-sync-alt:hover { | |
| transform: rotate(180deg); | |
| } | |
| .fa-chart-bar, .fa-chart-line, .fa-chart-pie { | |
| transition: transform 0.3s ease; | |
| } | |
| .fa-chart-bar:hover, .fa-chart-line:hover, .fa-chart-pie:hover { | |
| transform: scale(1.2); | |
| } | |
| .fa-download { | |
| animation: bounce 1s infinite; | |
| } | |
| @keyframes bounce { | |
| 0%, 100% { transform: translateY(0); } | |
| 50% { transform: translateY(-3px); } | |
| } | |
| /* Enhanced button hover effects with icons */ | |
| .btn:hover i { | |
| transform: scale(1.1); | |
| } | |
| /* Icon spacing */ | |
| .me-2 { | |
| margin-right: 0.5rem; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container mt-5"> | |
| <h1 class="text-center text-primary mb-4"> | |
| <i class="fas fa-university me-2"></i> Faculty Data Analysis | |
| </h1> | |
| <!-- File Upload Form --> | |
| <form method="POST" enctype="multipart/form-data" class="mb-5"> | |
| <div class="mb-3"> | |
| <label for="faculty_file" class="form-label"> | |
| <i class="fas fa-file-upload me-2"></i>Upload Faculty Data CSV | |
| </label> | |
| <input type="file" id="faculty_file" name="faculty_file" class="form-control" accept=".csv" required> | |
| </div> | |
| <!-- Input Fields for Student Counts --> | |
| {% if departments %} | |
| <h3 class="mt-4"> | |
| <i class="fas fa-users me-2"></i>Enter Student Counts by Department | |
| </h3> | |
| {% for department in departments %} | |
| <div class="mb-3"> | |
| <label for="students_{{ department }}" class="form-label">{{ department }}</label> | |
| <input type="number" id="students_{{ department }}" name="students_{{ department }}" class="form-control" min="0" required> | |
| </div> | |
| {% endfor %} | |
| {% endif %} | |
| <button type="submit" class="btn btn-primary w-100"> | |
| <i class="fas fa-chart-bar me-2"></i>Analyze | |
| </button> | |
| </form> | |
| <!-- Error Message --> | |
| {% if error %} | |
| <div class="alert alert-danger text-center"> | |
| {{ error }} | |
| </div> | |
| {% endif %} | |
| <!-- Download Graded CSV --> | |
| {% if graded_csv %} | |
| <div class="text-end mb-4"> | |
| <a href="data:text/csv;base64,{{ graded_csv }}" download="graded_faculty_data.csv" class="btn btn-success"> | |
| <i class="fas fa-download me-2"></i>Download Graded CSV | |
| </a> | |
| </div> | |
| {% endif %} | |
| {% if plots %} | |
| <h2 class="text-center text-secondary"> | |
| <i class="fas fa-chart-line me-2"></i>Visualizations with Insights | |
| </h2> | |
| <div class="accordion" id="plotsAccordion"> | |
| {% for plot_title, plot_html in plots.items() %} | |
| <div class="accordion-item"> | |
| <h2 class="accordion-header" id="heading-{{ loop.index }}"> | |
| <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" | |
| data-bs-target="#collapse-{{ loop.index }}" aria-expanded="false" aria-controls="collapse-{{ loop.index }}"> | |
| {{ plot_title }} | |
| </button> | |
| </h2> | |
| <div id="collapse-{{ loop.index }}" class="accordion-collapse collapse" | |
| aria-labelledby="heading-{{ loop.index }}" data-bs-parent="#plotsAccordion"> | |
| <div class="accordion-body"> | |
| <div class="row"> | |
| <!-- Graph Section --> | |
| <div class="col-md-7"> | |
| <div>{{ plot_html | safe }}</div> | |
| </div> | |
| <!-- Gemini Insight Section --> | |
| <div class="col-md-5"> | |
| <div class="card h-100"> | |
| <div class="card-header bg-info text-white"> | |
| <strong>Insights for {{ plot_title }}</strong> | |
| </div> | |
| <div class="card-body"> | |
| <p>{{ gemini_insights[plot_title] }}</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {% endfor %} | |
| </div> | |
| {% endif %} | |
| <!-- Department Tables Section --> | |
| {% if department_tables %} | |
| <h2 class="text-center text-warning mt-5">Faculty Data by Department</h2> | |
| {% for department, table in department_tables.items() %} | |
| <div class="mb-4"> | |
| <h3 class="text-center text-secondary">{{ department }}</h3> | |
| <div class="table-responsive"> | |
| <table class="table table-striped table-bordered"> | |
| <thead> | |
| <tr> | |
| {% for col in table.columns %} | |
| <th>{{ col }}</th> | |
| {% endfor %} | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {% for row in table.rows %} | |
| <tr> | |
| {% for cell in row %} | |
| <td>{{ cell }}</td> | |
| {% endfor %} | |
| </tr> | |
| {% endfor %} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| {% endfor %} | |
| {% endif %} | |
| <!-- Deficiency Table Section --> | |
| {% if deficiency_table %} | |
| <h2 class="text-center text-danger mt-5">Deficiency Comparison: Faculty vs Students</h2> | |
| <div class="table-responsive"> | |
| {{ deficiency_table | safe }} | |
| </div> | |
| {% endif %} | |
| <!-- SWOT Analysis Results --> | |
| {% if swot_results %} | |
| <div class="card mt-5"> | |
| <div class="card-header bg-info text-white"> | |
| <h3 class="mb-0"><i class="fas fa-chart-pie me-2"></i>SWOT Analysis Results</h3> | |
| </div> | |
| <div class="card-body"> | |
| <div class="row"> | |
| <div class="col-md-6"> | |
| <h4 class="text-success"><i class="fas fa-star me-2"></i>Strengths</h4> | |
| <ul> | |
| {% for strength in swot_results.strengths %} | |
| <li>{{ strength }}</li> | |
| {% endfor %} | |
| </ul> | |
| </div> | |
| <div class="col-md-6"> | |
| <h4 class="text-danger"><i class="fas fa-exclamation-triangle me-2"></i>Weaknesses</h4> | |
| <ul> | |
| {% for weakness in swot_results.weaknesses %} | |
| <li>{{ weakness }}</li> | |
| {% endfor %} | |
| </ul> | |
| </div> | |
| </div> | |
| <div class="row"> | |
| <div class="col-md-6"> | |
| <h4 class="text-primary"><i class="fas fa-lightbulb me-2"></i>Opportunities</h4> | |
| <ul> | |
| {% for opportunity in swot_results.opportunities %} | |
| <li>{{ opportunity }}</li> | |
| {% endfor %} | |
| </ul> | |
| </div> | |
| <div class="col-md-6"> | |
| <h4 class="text-warning"><i class="fas fa-shield-alt me-2"></i>Threats</h4> | |
| <ul> | |
| {% for threat in swot_results.threats %} | |
| <li>{{ threat }}</li> | |
| {% endfor %} | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {% endif %} | |
| <button class="btn btn-secondary print-btn" onclick="window.print()"> | |
| <i class="fas fa-print"></i> Get Report | |
| </button> | |
| <!-- Scripts --> | |
| <script> | |
| // Initialize Bootstrap Popovers | |
| document.addEventListener('DOMContentLoaded', function () { | |
| var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); | |
| var popoverList = popoverTriggerList.map(function (popoverTriggerEl) { | |
| return new bootstrap.Popover(popoverTriggerEl, { | |
| html: true, | |
| container: 'body' | |
| }); | |
| }); | |
| }); | |
| // Range slider value display | |
| document.querySelectorAll('.custom-range').forEach(range => { | |
| const valueDisplay = document.getElementById(range.id.replace('range', 'value')); | |
| range.addEventListener('input', (e) => { | |
| valueDisplay.textContent = e.target.value; | |
| }); | |
| }); | |
| // Form submission loading state | |
| document.querySelector('form').addEventListener('submit', function(e) { | |
| this.classList.add('loading'); | |
| }); | |
| // Enhanced print functionality | |
| function printReport() { | |
| // Expand all accordion items before printing | |
| const accordionItems = document.querySelectorAll('.accordion-collapse'); | |
| accordionItems.forEach(item => { | |
| item.classList.add('show'); | |
| }); | |
| // Wait for any graphs to finish rendering | |
| setTimeout(() => { | |
| window.print(); | |
| // Restore accordion state after printing | |
| accordionItems.forEach(item => { | |
| item.classList.remove('show'); | |
| }); | |
| }, 500); | |
| } | |
| // Add click handler to print button | |
| document.querySelector('.print-btn').addEventListener('click', printReport); | |
| // Add keyboard shortcut (Ctrl/Cmd + P) | |
| document.addEventListener('keydown', function(e) { | |
| if ((e.ctrlKey || e.metaKey) && e.key === 'p') { | |
| e.preventDefault(); | |
| printReport(); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |