COMPANY_DASHBOARD / templates /job_details.html
pranit144's picture
Upload 12 files
1eabbf4 verified
<!DOCTYPE html>
<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) !important;
}
/* ======= 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) !important;
color: white;
}
.card-header.bg-secondary {
background-color: var(--secondary-color) !important;
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 !important;
}
.card {
box-shadow: none !important;
border: 1px solid #dee2e6 !important;
}
.container-fluid {
width: 100% !important;
max-width: none !important;
}
}
</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>