Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>{{ job.title }} | Job Details</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://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css"> | |
| <style> | |
| /* Campus Recruitment Portal - Main Stylesheet | |
| This file contains styles for all pages in the application */ | |
| /* ======= Global Styles ======= */ | |
| :root { | |
| --primary-color: #0d6efd; | |
| --primary-dark: #0b5ed7; | |
| --secondary-color: #6c757d; | |
| --success-color: #198754; | |
| --info-color: #0dcaf0; | |
| --warning-color: #ffc107; | |
| --danger-color: #dc3545; | |
| --light-color: #f8f9fa; | |
| --dark-color: #212529; | |
| --border-radius: 0.375rem; | |
| --box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); | |
| } | |
| body { | |
| font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; | |
| background-color: #f5f5f5; | |
| color: #333; | |
| } | |
| /* ======= Typography ======= */ | |
| h1, h2, h3, h4, h5, h6 { | |
| font-weight: 600; | |
| margin-bottom: 1rem; | |
| } | |
| .text-primary { | |
| color: var(--primary-color) ; | |
| } | |
| /* ======= Layout Components ======= */ | |
| .main-container { | |
| padding-top: 1.5rem; | |
| padding-bottom: 2rem; | |
| } | |
| .card { | |
| border-radius: var(--border-radius); | |
| box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); | |
| margin-bottom: 1.5rem; | |
| border: none; | |
| } | |
| .card-header { | |
| background-color: rgba(0, 0, 0, 0.03); | |
| border-bottom: 1px solid rgba(0, 0, 0, 0.125); | |
| padding: 0.75rem 1.25rem; | |
| } | |
| .card-header.bg-primary { | |
| background-color: var(--primary-color) ; | |
| color: white; | |
| } | |
| .card-header.bg-secondary { | |
| background-color: var(--secondary-color) ; | |
| color: white; | |
| } | |
| .card-body { | |
| padding: 1.25rem; | |
| } | |
| .card-footer { | |
| padding: 0.75rem 1.25rem; | |
| background-color: rgba(0, 0, 0, 0.03); | |
| border-top: 1px solid rgba(0, 0, 0, 0.125); | |
| } | |
| /* ======= Navigation ======= */ | |
| .navbar { | |
| padding: 0.5rem 1rem; | |
| box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); | |
| } | |
| .navbar-brand { | |
| font-weight: 600; | |
| font-size: 1.25rem; | |
| } | |
| .nav-link { | |
| padding: 0.5rem 1rem; | |
| transition: all 0.2s ease-in-out; | |
| } | |
| .nav-link:hover { | |
| opacity: 0.85; | |
| } | |
| .nav-link.active { | |
| font-weight: 600; | |
| background-color: rgba(255, 255, 255, 0.1); | |
| } | |
| /* ======= Forms ======= */ | |
| .form-control, .form-select { | |
| border-radius: var(--border-radius); | |
| padding: 0.375rem 0.75rem; | |
| border: 1px solid #ced4da; | |
| transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; | |
| } | |
| .form-control:focus, .form-select:focus { | |
| border-color: rgba(13, 110, 253, 0.5); | |
| box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); | |
| } | |
| .form-label { | |
| margin-bottom: 0.5rem; | |
| font-weight: 500; | |
| } | |
| .form-text { | |
| color: #6c757d; | |
| margin-top: 0.25rem; | |
| } | |
| /* ======= Login Page ======= */ | |
| .login-container { | |
| min-height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .login-card { | |
| width: 100%; | |
| max-width: 450px; | |
| box-shadow: var(--box-shadow); | |
| } | |
| .demo-credentials { | |
| background-color: #f8f9fa; | |
| padding: 0.75rem; | |
| border-radius: var(--border-radius); | |
| margin-top: 1rem; | |
| } | |
| /* ======= Dashboard ======= */ | |
| .dashboard-stats { | |
| padding: 1.5rem; | |
| background-color: white; | |
| box-shadow: var(--box-shadow); | |
| border-radius: var(--border-radius); | |
| margin-bottom: 1.5rem; | |
| } | |
| .stats-card { | |
| text-align: center; | |
| padding: 1.25rem; | |
| border: 1px solid rgba(0, 0, 0, 0.125); | |
| border-radius: var(--border-radius); | |
| background-color: white; | |
| } | |
| .stats-icon { | |
| font-size: 2rem; | |
| margin-bottom: 0.75rem; | |
| } | |
| .stats-value { | |
| font-size: 2rem; | |
| font-weight: 600; | |
| margin-bottom: 0.25rem; | |
| } | |
| .stats-label { | |
| color: #6c757d; | |
| font-size: 0.875rem; | |
| } | |
| /* ======= Filter Section ======= */ | |
| .filter-section { | |
| background-color: white; | |
| border-radius: var(--border-radius); | |
| margin-bottom: 1.5rem; | |
| } | |
| .filter-heading { | |
| font-weight: 600; | |
| margin-bottom: 1rem; | |
| } | |
| .filter-group { | |
| margin-bottom: 1.25rem; | |
| } | |
| /* ======= Tables ======= */ | |
| .table { | |
| margin-bottom: 0; | |
| } | |
| .table-striped tbody tr:nth-of-type(odd) { | |
| background-color: rgba(0, 0, 0, 0.02); | |
| } | |
| .table th { | |
| font-weight: 600; | |
| border-top: none; | |
| } | |
| .table-hover tbody tr:hover { | |
| background-color: rgba(13, 110, 253, 0.05); | |
| } | |
| /* ======= Badges ======= */ | |
| .badge { | |
| padding: 0.35em 0.65em; | |
| font-weight: 500; | |
| border-radius: 0.25rem; | |
| margin-right: 0.25rem; | |
| } | |
| .skills-container { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 0.25rem; | |
| } | |
| /* ======= Visualization Section ======= */ | |
| .visualization-card { | |
| height: 100%; | |
| } | |
| .visualization-chart { | |
| width: 100%; | |
| height: auto; | |
| } | |
| /* ======= Job Listings ======= */ | |
| .job-card { | |
| transition: transform 0.2s ease-in-out; | |
| height: 100%; | |
| } | |
| .job-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); | |
| } | |
| .job-title { | |
| font-weight: 600; | |
| margin-bottom: 0.5rem; | |
| } | |
| .job-location { | |
| color: #6c757d; | |
| font-size: 0.875rem; | |
| } | |
| .job-skills { | |
| margin-top: 0.5rem; | |
| margin-bottom: 1rem; | |
| } | |
| /* ======= Student Details ======= */ | |
| .student-profile { | |
| padding: 1.5rem; | |
| } | |
| .student-name { | |
| font-size: 1.5rem; | |
| font-weight: 600; | |
| margin-bottom: 0.5rem; | |
| } | |
| .student-info { | |
| margin-bottom: 0.5rem; | |
| } | |
| .section-heading { | |
| font-weight: 600; | |
| margin-bottom: 0.75rem; | |
| border-bottom: 1px solid #e9ecef; | |
| padding-bottom: 0.5rem; | |
| } | |
| /* ======= AI Insights ======= */ | |
| .insights-section { | |
| background-color: #f8f9fa; | |
| padding: 1.5rem; | |
| border-radius: var(--border-radius); | |
| margin-bottom: 1.5rem; | |
| } | |
| .insights-text { | |
| white-space: pre-wrap; | |
| max-height: 500px; | |
| overflow-y: auto; | |
| padding: 1rem; | |
| background-color: white; | |
| border: 1px solid #dee2e6; | |
| border-radius: var(--border-radius); | |
| } | |
| /* ======= Button Styles ======= */ | |
| .btn { | |
| padding: 0.375rem 0.75rem; | |
| border-radius: var(--border-radius); | |
| font-weight: 500; | |
| transition: all 0.2s ease-in-out; | |
| } | |
| .btn-primary { | |
| background-color: var(--primary-color); | |
| border-color: var(--primary-color); | |
| } | |
| .btn-primary:hover { | |
| background-color: var(--primary-dark); | |
| border-color: var(--primary-dark); | |
| } | |
| .btn-outline-primary { | |
| color: var(--primary-color); | |
| border-color: var(--primary-color); | |
| } | |
| .btn-outline-primary:hover { | |
| background-color: var(--primary-color); | |
| color: white; | |
| border-color: var(--primary-color); | |
| } | |
| .btn-sm { | |
| padding: 0.25rem 0.5rem; | |
| font-size: 0.875rem; | |
| } | |
| /* ======= Modals ======= */ | |
| .modal-header { | |
| padding: 1rem; | |
| border-bottom: 1px solid #dee2e6; | |
| } | |
| .modal-body { | |
| padding: 1.5rem; | |
| } | |
| .modal-footer { | |
| padding: 1rem; | |
| border-top: 1px solid #dee2e6; | |
| } | |
| /* ======= Alerts ======= */ | |
| .alert { | |
| padding: 1rem; | |
| margin-bottom: 1rem; | |
| border-radius: var(--border-radius); | |
| } | |
| /* ======= Spinner ======= */ | |
| .spinner-container { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| padding: 2rem; | |
| } | |
| .spinner-border { | |
| width: 3rem; | |
| height: 3rem; | |
| } | |
| /* ======= Responsive Adjustments ======= */ | |
| @media (max-width: 768px) { | |
| .card-body { | |
| padding: 1rem; | |
| } | |
| .navbar-brand { | |
| font-size: 1rem; | |
| } | |
| .stats-value { | |
| font-size: 1.5rem; | |
| } | |
| .stats-icon { | |
| font-size: 1.5rem; | |
| } | |
| .table-responsive { | |
| border: 0; | |
| } | |
| } | |
| /* ======= Print Styles ======= */ | |
| @media print { | |
| .navbar, .card-header, .btn, .filter-section { | |
| display: none ; | |
| } | |
| .card { | |
| box-shadow: none ; | |
| border: 1px solid #dee2e6 ; | |
| } | |
| .container-fluid { | |
| width: 100% ; | |
| max-width: none ; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Navigation --> | |
| <nav class="navbar navbar-expand-lg navbar-dark bg-primary"> | |
| <div class="container-fluid"> | |
| <a class="navbar-brand" href="{{ url_for('dashboard') }}">{{ company_name }} - Recruitment Portal</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 me-auto"> | |
| <li class="nav-item"> | |
| <a class="nav-link" href="{{ url_for('dashboard') }}">Dashboard</a> | |
| </li> | |
| <li class="nav-item"> | |
| <a class="nav-link active" href="{{ url_for('jobs') }}">Job Postings</a> | |
| </li> | |
| </ul> | |
| <ul class="navbar-nav"> | |
| <li class="nav-item"> | |
| <a class="nav-link" href="{{ url_for('logout') }}"> | |
| <i class="bi bi-box-arrow-right"></i> Logout | |
| </a> | |
| </li> | |
| </ul> | |
| </div> | |
| </div> | |
| </nav> | |
| <!-- Main Content --> | |
| <div class="container-fluid mt-3"> | |
| <!-- Job Details Header --> | |
| <div class="card mb-3"> | |
| <div class="card-header bg-primary text-white"> | |
| <div class="d-flex justify-content-between align-items-center"> | |
| <h5 class="card-title mb-0">{{ job.title }}</h5> | |
| <a href="{{ url_for('jobs') }}" class="btn btn-sm btn-light"> | |
| <i class="bi bi-arrow-left"></i> Back to Jobs | |
| </a> | |
| </div> | |
| </div> | |
| <div class="card-body"> | |
| <div class="row"> | |
| <div class="col-md-8"> | |
| <h6>Job Description</h6> | |
| <p>{{ job.description }}</p> | |
| </div> | |
| <div class="col-md-4"> | |
| <h6>Job Details</h6> | |
| <ul class="list-group list-group-flush"> | |
| <li class="list-group-item"><strong>Required Skills:</strong> {{ job.required_skills|join(', ') }}</li> | |
| <li class="list-group-item"><strong>Minimum CGPA:</strong> {{ job.min_cgpa }}</li> | |
| <li class="list-group-item"><strong>Experience Level:</strong> {{ job.experience_level }} years</li> | |
| <li class="list-group-item"><strong>Location:</strong> {{ job.location }}</li> | |
| <li class="list-group-item"><strong>Posted Date:</strong> {{ job.posted_date }}</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Eligible Candidates --> | |
| <div class="card"> | |
| <div class="card-header bg-secondary text-white"> | |
| <div class="d-flex justify-content-between align-items-center"> | |
| <h5 class="card-title mb-0">Eligible Candidates ({{ candidates|length }})</h5> | |
| <div> | |
| <button class="btn btn-sm btn-light me-2" id="compareSelectedBtn" disabled> | |
| <i class="bi bi-bar-chart-line"></i> Compare Selected | |
| </button> | |
| <button class="btn btn-sm btn-light" id="exportSelectedBtn" disabled> | |
| <i class="bi bi-download"></i> Export Selected | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="card-body"> | |
| <form id="candidatesForm"> | |
| <div class="table-responsive"> | |
| <table class="table table-striped table-hover"> | |
| <thead> | |
| <tr> | |
| <th><input type="checkbox" id="selectAll" class="form-check-input"></th> | |
| <th>Name</th> | |
| <th>Department</th> | |
| <th>CGPA</th> | |
| <th>Skills</th> | |
| <th>Projects</th> | |
| <th>Internships</th> | |
| <th>Action</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {% for candidate in candidates %} | |
| <tr> | |
| <td><input type="checkbox" name="selected_students" value="{{ candidate.email }}" class="form-check-input candidate-checkbox"></td> | |
| <td>{{ candidate.full_name }}</td> | |
| <td>{{ candidate.department }}</td> | |
| <td>{{ candidate.cgpa }}</td> | |
| <td> | |
| <div class="skills-container"> | |
| {% if candidate.programming_languages %} | |
| {% for skill in candidate.programming_languages.split(',')[:3] %} | |
| <span class="badge bg-primary">{{ skill.strip() }}</span> | |
| {% endfor %} | |
| {% if candidate.programming_languages.split(',')|length > 3 %} | |
| <span class="badge bg-secondary">+{{ candidate.programming_languages.split(',')|length - 3 }}</span> | |
| {% endif %} | |
| {% endif %} | |
| </div> | |
| </td> | |
| <td>{{ candidate.projects | truncate(30) if candidate.projects else 'N/A' }}</td> | |
| <td>{{ candidate.internships | truncate(30) if candidate.internships else 'N/A' }}</td> | |
| <td> | |
| <button type="button" class="btn btn-sm btn-outline-primary view-details-btn" data-email="{{ candidate.email }}"> | |
| <i class="bi bi-eye"></i> Details | |
| </button> | |
| </td> | |
| </tr> | |
| {% endfor %} | |
| </tbody> | |
| </table> | |
| </div> | |
| </form> | |
| {% if not candidates %} | |
| <div class="alert alert-info text-center"> | |
| No eligible candidates found for this job posting. Consider adjusting the job requirements. | |
| </div> | |
| {% endif %} | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Student Details Modal --> | |
| <div class="modal fade" id="studentDetailsModal" tabindex="-1"> | |
| <div class="modal-dialog modal-lg"> | |
| <div class="modal-content"> | |
| <div class="modal-header bg-primary text-white"> | |
| <h5 class="modal-title">Student Details</h5> | |
| <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button> | |
| </div> | |
| <div class="modal-body" id="studentDetailsContent"> | |
| <div class="text-center"> | |
| <div class="spinner-border" role="status"> | |
| <span class="visually-hidden">Loading...</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="modal-footer"> | |
| <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- AI Insights Modal --> | |
| <div class="modal fade" id="aiInsightsModal" tabindex="-1"> | |
| <div class="modal-dialog modal-lg"> | |
| <div class="modal-content"> | |
| <div class="modal-header bg-success text-white"> | |
| <h5 class="modal-title">AI Insights</h5> | |
| <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button> | |
| </div> | |
| <div class="modal-body" id="aiInsightsContent"> | |
| <div class="mb-3"> | |
| <label for="roleInput" class="form-label">Role to analyze for:</label> | |
| <input type="text" class="form-control" id="roleInput" value="{{ job.title }}"> | |
| </div> | |
| <div id="insightsResult" class="d-none"> | |
| <div class="alert alert-info"> | |
| <h5>Analysis Results:</h5> | |
| <div id="insightsText" class="insights-text"></div> | |
| </div> | |
| </div> | |
| <div id="insightsLoading" class="d-none text-center"> | |
| <div class="spinner-border" role="status"> | |
| <span class="visually-hidden">Loading...</span> | |
| </div> | |
| <p>Generating insights... This may take a moment.</p> | |
| </div> | |
| </div> | |
| <div class="modal-footer"> | |
| <button type="button" class="btn btn-primary" id="generateInsightsBtn">Generate Insights</button> | |
| <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Handle checkbox selection | |
| const selectAllCheckbox = document.getElementById('selectAll'); | |
| const candidateCheckboxes = document.querySelectorAll('.candidate-checkbox'); | |
| const compareSelectedBtn = document.getElementById('compareSelectedBtn'); | |
| const exportSelectedBtn = document.getElementById('exportSelectedBtn'); | |
| selectAllCheckbox.addEventListener('change', function() { | |
| candidateCheckboxes.forEach(checkbox => { | |
| checkbox.checked = selectAllCheckbox.checked; | |
| }); | |
| updateButtonState(); | |
| }); | |
| candidateCheckboxes.forEach(checkbox => { | |
| checkbox.addEventListener('change', updateButtonState); | |
| }); | |
| function updateButtonState() { | |
| const selectedCount = document.querySelectorAll('.candidate-checkbox:checked').length; | |
| compareSelectedBtn.disabled = selectedCount < 2; | |
| exportSelectedBtn.disabled = selectedCount === 0; | |
| } | |
| // Handle view details button | |
| const viewDetailsButtons = document.querySelectorAll('.view-details-btn'); | |
| viewDetailsButtons.forEach(button => { | |
| button.addEventListener('click', function() { | |
| const email = this.getAttribute('data-email'); | |
| const studentData = findStudentByEmail(email); | |
| showStudentDetails(studentData); | |
| }); | |
| }); | |
| function findStudentByEmail(email) { | |
| // In a real implementation, you might want to fetch this data from the server | |
| // For now, we'll use the data we already have on the page | |
| return {{ candidates|tojson|safe }}.find(student => student.email === email); | |
| } | |
| function showStudentDetails(student) { | |
| if (!student) return; | |
| const modal = new bootstrap.Modal(document.getElementById('studentDetailsModal')); | |
| const content = document.getElementById('studentDetailsContent'); | |
| let htmlContent = ` | |
| <div class="row"> | |
| <div class="col-md-6"> | |
| <h5>${student.full_name}</h5> | |
| <p><strong>Email:</strong> ${student.email}</p> | |
| <p><strong>Department:</strong> ${student.department}</p> | |
| <p><strong>CGPA:</strong> ${student.cgpa}</p> | |
| <p><strong>Backlogs:</strong> ${student.backlogs || 0}</p> | |
| </div> | |
| <div class="col-md-6"> | |
| <h6>Skills</h6> | |
| <div> | |
| <strong>Programming:</strong> ${student.programming_languages || 'N/A'} | |
| </div> | |
| <div> | |
| <strong>Tools & Technologies:</strong> ${student.tools_technologies || 'N/A'} | |
| </div> | |
| <div> | |
| <strong>Soft Skills:</strong> ${student.soft_skills || 'N/A'} | |
| </div> | |
| </div> | |
| </div> | |
| <hr> | |
| <div class="row mt-3"> | |
| <div class="col-md-6"> | |
| <h6>Projects</h6> | |
| <p>${student.projects || 'No projects listed'}</p> | |
| </div> | |
| <div class="col-md-6"> | |
| <h6>Internships</h6> | |
| <p>${student.internships || 'No internships listed'}</p> | |
| </div> | |
| </div> | |
| <div class="row mt-3"> | |
| <div class="col-md-6"> | |
| <h6>Certifications</h6> | |
| <p>${student.certifications || 'No certifications listed'}</p> | |
| </div> | |
| <div class="col-md-6"> | |
| <h6>Hackathons & Competitions</h6> | |
| <p>${student.hackathons || 'None listed'}</p> | |
| </div> | |
| </div> | |
| <div class="row mt-3"> | |
| <div class="col-md-6"> | |
| <h6>Preferred Roles</h6> | |
| <p>${student.preferred_roles || 'Not specified'}</p> | |
| </div> | |
| <div class="col-md-6"> | |
| <h6>Preferred Location</h6> | |
| <p>${student.preferred_location || 'Not specified'}</p> | |
| </div> | |
| </div> | |
| <div class="row mt-3"> | |
| <div class="col-12"> | |
| <h6>Strengths & Weaknesses</h6> | |
| <p><strong>Strengths:</strong> ${student.strengths || 'Not specified'}</p> | |
| <p><strong>Weaknesses:</strong> ${student.weaknesses || 'Not specified'}</p> | |
| </div> | |
| </div>`; | |
| content.innerHTML = htmlContent; | |
| modal.show(); | |
| } | |
| // Handle compare selected button | |
| compareSelectedBtn.addEventListener('click', function() { | |
| const selectedEmails = Array.from(document.querySelectorAll('.candidate-checkbox:checked')) | |
| .map(checkbox => checkbox.value); | |
| if (selectedEmails.length < 2) { | |
| alert('Please select at least 2 candidates to compare.'); | |
| return; | |
| } | |
| // Show AI insights modal | |
| const modal = new bootstrap.Modal(document.getElementById('aiInsightsModal')); | |
| modal.show(); | |
| }); | |
| // Handle generate insights button | |
| document.getElementById('generateInsightsBtn').addEventListener('click', function() { | |
| const selectedEmails = Array.from(document.querySelectorAll('.candidate-checkbox:checked')) | |
| .map(checkbox => checkbox.value); | |
| const role = document.getElementById('roleInput').value; | |
| const loadingDiv = document.getElementById('insightsLoading'); | |
| const resultDiv = document.getElementById('insightsResult'); | |
| const insightsTextDiv = document.getElementById('insightsText'); | |
| loadingDiv.classList.remove('d-none'); | |
| resultDiv.classList.add('d-none'); | |
| // Make AJAX request to get AI insights | |
| const formData = new FormData(); | |
| selectedEmails.forEach(email => { | |
| formData.append('selected_students', email); | |
| }); | |
| formData.append('role', role); | |
| fetch('/ai_insights', { | |
| method: 'POST', | |
| body: formData | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| loadingDiv.classList.add('d-none'); | |
| resultDiv.classList.remove('d-none'); | |
| if (data.error) { | |
| insightsTextDiv.innerHTML = `<div class="alert alert-danger">${data.error}</div>`; | |
| } else { | |
| // Format the insights with Markdown or HTML | |
| insightsTextDiv.innerHTML = formatInsights(data.insights); | |
| } | |
| }) | |
| .catch(error => { | |
| loadingDiv.classList.add('d-none'); | |
| resultDiv.classList.remove('d-none'); | |
| insightsTextDiv.innerHTML = `<div class="alert alert-danger">Error: ${error.message}</div>`; | |
| }); | |
| }); | |
| function formatInsights(insights) { | |
| // Convert line breaks to HTML breaks and preserve formatting | |
| return insights | |
| .replace(/\n\n/g, '</p><p>') | |
| .replace(/\n/g, '<br>') | |
| .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') | |
| .replace(/\*(.*?)\*/g, '<em>$1</em>'); | |
| } | |
| // Handle export selected button | |
| exportSelectedBtn.addEventListener('click', function() { | |
| const selectedEmails = Array.from(document.querySelectorAll('.candidate-checkbox:checked')) | |
| .map(checkbox => checkbox.value); | |
| if (selectedEmails.length === 0) { | |
| alert('Please select at least one candidate to export.'); | |
| return; | |
| } | |
| // Get the full student data for selected emails | |
| const selectedStudents = {{ candidates|tojson|safe }}.filter( | |
| student => selectedEmails.includes(student.email) | |
| ); | |
| // Submit form to export endpoint | |
| const form = document.createElement('form'); | |
| form.method = 'POST'; | |
| form.action = '/export_filtered'; | |
| const input = document.createElement('input'); | |
| input.type = 'hidden'; | |
| input.name = 'filtered_students'; | |
| input.value = JSON.stringify(selectedStudents); | |
| form.appendChild(input); | |
| document.body.appendChild(form); | |
| form.submit(); | |
| document.body.removeChild(form); | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |